优化功能
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:
@@ -27,9 +27,9 @@ const formSchema = computed((): VbenFormSchema[] => {
|
||||
const schema: VbenFormSchema[] = [
|
||||
// 1. Radio选择组(最上面)
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
component: 'VbenSelect',
|
||||
componentProps: {
|
||||
optionType: 'button',
|
||||
// optionType: 'button',
|
||||
options: [
|
||||
{ label: '手机号登录', value: 'phone' },
|
||||
{ label: '登录账号登录', value: 'login_account' },
|
||||
|
||||
@@ -12,7 +12,6 @@ import { useThemeStore } from '../stores/theme.ts';
|
||||
import { useUserStore } from '../stores/user.ts';
|
||||
import { sendMessage } from '../utils/request.ts';
|
||||
import { usePrescriptionStore } from '#/store/prescription';
|
||||
import { getProductListDoctorReception } from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
// 根据路由判断是在线复诊-药店还是在线复诊-诊所
|
||||
const route = useRoute();
|
||||
@@ -21,8 +20,8 @@ const isClinicModule = computed(() => {
|
||||
});
|
||||
|
||||
// 动态导入对应的API和组件
|
||||
import { endConsultation as endConsultationPharmacy } from '#/views/doctor/online-consultation/api/index';
|
||||
import { endConsultation as endConsultationClinic } from '#/views/doctor/online-consultation-clinic/api/index';
|
||||
import { endConsultation as endConsultationPharmacy, getDrugsByRegisterId as getDrugsByRegisterIdPharmacy } from '#/views/doctor/online-consultation/api/index';
|
||||
import { endConsultation as endConsultationClinic, getDrugsByRegisterId as getDrugsByRegisterIdClinic } from '#/views/doctor/online-consultation-clinic/api/index';
|
||||
import QuickReplyBubblesPharmacy from '#/views/doctor/online-consultation/components/QuickReplyBubbles.vue';
|
||||
import QuickReplyBubblesClinic from '#/views/doctor/online-consultation-clinic/components/QuickReplyBubbles.vue';
|
||||
|
||||
@@ -31,6 +30,10 @@ const endConsultation = computed(() => {
|
||||
return isClinicModule.value ? endConsultationClinic : endConsultationPharmacy;
|
||||
});
|
||||
|
||||
const getDrugsByRegisterId = computed(() => {
|
||||
return isClinicModule.value ? getDrugsByRegisterIdClinic : getDrugsByRegisterIdPharmacy;
|
||||
});
|
||||
|
||||
const QuickReplyBubbles = computed(() => {
|
||||
return isClinicModule.value ? QuickReplyBubblesClinic : QuickReplyBubblesPharmacy;
|
||||
});
|
||||
@@ -349,6 +352,7 @@ const handleQuickReplySelect = (content) => {
|
||||
|
||||
// 判断是否显示在线复诊相关按钮
|
||||
const showOnlineConsultationButtons = computed(() => {
|
||||
console.log('检查是否显示在线复诊相关按钮:', chatStore.currentFriend);
|
||||
// 检查是否有 register_id,如果有则说明是在线复诊场景
|
||||
return !!chatStore.currentFriend?.register_id;
|
||||
});
|
||||
@@ -368,16 +372,6 @@ const handleAddPatientProductsToPrescription = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 从聊天记录中筛选出所有 message_type=11 的消息(患者就诊经历)
|
||||
const experienceMessages = chatStore.messages.filter(
|
||||
(msg) => msg.message_type === 11
|
||||
);
|
||||
|
||||
if (experienceMessages.length === 0) {
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取诊所ID
|
||||
let storeId = prescriptionStore.myStoreId;
|
||||
if (!storeId || storeId === 0) {
|
||||
@@ -389,64 +383,34 @@ const handleAddPatientProductsToPrescription = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 按时间排序,取最新的就诊经历
|
||||
const sortedMessages = [...experienceMessages].sort((a, b) => {
|
||||
const timeA = a.created_at ? new Date(a.created_at).getTime() : (a.timestamp || 0);
|
||||
const timeB = b.created_at ? new Date(b.created_at).getTime() : (b.timestamp || 0);
|
||||
return timeB - timeA; // 降序排列,最新的在前
|
||||
});
|
||||
|
||||
const latestMessage = sortedMessages[0];
|
||||
try {
|
||||
// 解析最新消息的内容
|
||||
let experienceData;
|
||||
try {
|
||||
experienceData = typeof latestMessage.message_content === 'string'
|
||||
? JSON.parse(latestMessage.message_content)
|
||||
: latestMessage.message_content;
|
||||
} catch (e) {
|
||||
console.error('解析就诊经历消息失败:', e);
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取药品名称
|
||||
const drugName = experienceData.drug_name;
|
||||
if (!drugName) {
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过药品名称搜索药品
|
||||
const res = await getProductListDoctorReception({
|
||||
store_id: storeId,
|
||||
type: 2, // 中成(西)药
|
||||
name: drugName,
|
||||
});
|
||||
|
||||
if (!res || res.length === 0) {
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用第一个匹配的药品
|
||||
const drugData = res[0];
|
||||
|
||||
// 检查药品是否已在处方中
|
||||
const existItem = prescriptionStore.currentDrugs.find(
|
||||
(item) => item.index_id === drugData.id,
|
||||
// 根据 register_id 获取药品信息
|
||||
const res = await getDrugsByRegisterId.value(
|
||||
prescriptionStore.currentRegisterId || chatStore.currentFriend?.register_id,
|
||||
storeId
|
||||
);
|
||||
if (existItem) {
|
||||
message.warning('该药品已在处方中');
|
||||
const drugList = res?.result || res?.data || res || [];
|
||||
|
||||
if (!drugList || drugList.length === 0) {
|
||||
message.warning('未找到药品信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加药品到处方单
|
||||
const success = prescriptionStore.addProducts(drugData);
|
||||
if (success) {
|
||||
message.success(`已将${drugData.drug.drug_name}添加到处方单`);
|
||||
// 遍历药品列表,添加到处方单
|
||||
let addedCount = 0;
|
||||
for (const drugData of drugList) {
|
||||
const success = prescriptionStore.addProducts(drugData);
|
||||
if (success) {
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (addedCount > 0) {
|
||||
message.success(`已成功添加 ${addedCount} 个药品到处方单`);
|
||||
// 触发打开处方模态框事件
|
||||
emit('openPrescription', prescriptionStore.currentRegisterId || chatStore.currentFriend?.register_id);
|
||||
} else {
|
||||
message.warning('所有药品已在处方中');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加药品失败:', error);
|
||||
@@ -522,13 +486,13 @@ const handleEndConsultation = () => {
|
||||
>
|
||||
<i class="fas fa-image"></i>
|
||||
</button>
|
||||
<button
|
||||
class="function-btn"
|
||||
@click="triggerFileInput('video')"
|
||||
title="发送视频"
|
||||
>
|
||||
<i class="fas fa-video"></i>
|
||||
</button>
|
||||
<!-- <button-->
|
||||
<!-- class="function-btn"-->
|
||||
<!-- @click="triggerFileInput('video')"-->
|
||||
<!-- title="发送视频"-->
|
||||
<!-- >-->
|
||||
<!-- <i class="fas fa-video"></i>-->
|
||||
<!-- </button>-->
|
||||
<button
|
||||
:class="{ active: showEmojiPicker }"
|
||||
class="function-btn"
|
||||
@@ -537,18 +501,18 @@ const handleEndConsultation = () => {
|
||||
>
|
||||
<i class="fas fa-smile"></i>
|
||||
</button>
|
||||
<button
|
||||
:class="{ recording: isRecording }"
|
||||
class="function-btn record-btn"
|
||||
title="按住录音"
|
||||
@mousedown="startRecording"
|
||||
@mouseleave="stopRecording"
|
||||
@mouseup="stopRecording"
|
||||
@touchend="stopRecording"
|
||||
@touchstart="startRecording"
|
||||
>
|
||||
<i class="fas fa-microphone"></i>
|
||||
</button>
|
||||
<!-- <button-->
|
||||
<!-- :class="{ recording: isRecording }"-->
|
||||
<!-- class="function-btn record-btn"-->
|
||||
<!-- title="按住录音"-->
|
||||
<!-- @mousedown="startRecording"-->
|
||||
<!-- @mouseleave="stopRecording"-->
|
||||
<!-- @mouseup="stopRecording"-->
|
||||
<!-- @touchend="stopRecording"-->
|
||||
<!-- @touchstart="startRecording"-->
|
||||
<!-- >-->
|
||||
<!-- <i class="fas fa-microphone"></i>-->
|
||||
<!-- </button>-->
|
||||
<!-- 添加药品按钮(仅在线复诊场景显示) -->
|
||||
<button
|
||||
v-if="showOnlineConsultationButtons"
|
||||
|
||||
@@ -133,9 +133,9 @@ onMounted(() => {
|
||||
<!-- 空状态 -->
|
||||
<div
|
||||
v-if="chatStore.messages.length === 0 && chatStore.currentFriend"
|
||||
class="py-10 text-center text-gray-500 dark:text-gray-400"
|
||||
class="flex h-full min-h-[300px] w-full items-center justify-center py-10 text-center text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<MessageOutlined class="mb-2 text-4xl" />
|
||||
<MessageOutlined class="mb-2 text-4xl mr-2" />
|
||||
<p>开始与 {{ chatStore.currentFriend.nick_name }} 对话吧!</p>
|
||||
</div>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -112,8 +112,8 @@ const openPrescriptionSourceModal = (id) => {
|
||||
</div>
|
||||
</template>
|
||||
<template #is-online="{ row }">
|
||||
<Tag v-if="row.is_online === 1" color="blue">在线问诊</Tag>
|
||||
<Tag v-else-if="row.is_online === 2" color="green">在线复诊</Tag>
|
||||
<Tag v-if="row.is_online == 1" color="blue">在线问诊</Tag>
|
||||
<Tag v-else-if="row.is_online == 2" color="green">在线复诊</Tag>
|
||||
<Tag v-else color="default">线下就诊</Tag>
|
||||
</template>
|
||||
<template #toolbar-tools></template>
|
||||
|
||||
@@ -254,7 +254,8 @@ const toggleFreeShipping = async (row: any, checked: boolean) => {
|
||||
</template>
|
||||
<template #is-online="{ row }">
|
||||
<Tag v-if="row.is_online === 1" color="blue">在线问诊</Tag>
|
||||
<Tag v-else-if="row.is_online === 2" color="green">在线复诊</Tag>
|
||||
<Tag v-else-if="row.is_online === 2" color="green">在线复诊(诊所)</Tag>
|
||||
<Tag v-else-if="row.is_online === 3" color="green">在线复诊(药店)</Tag>
|
||||
<Tag v-else color="default">线下就诊</Tag>
|
||||
</template>
|
||||
<template #delivery-method="{ row }">
|
||||
|
||||
@@ -72,3 +72,11 @@ export async function importWesternMedicineApi(data: Record<string, any>) {
|
||||
export async function updateZoneCategory(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}update-zone-category`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新最低调整金额
|
||||
* @param data
|
||||
*/
|
||||
export async function updateMinAdjustPrice(data: { id: number; min_adjust_price: number | null }) {
|
||||
return requestClient.post<any>(`${prefix}update-min-adjust-price`, data);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Form, InputNumber, message } from 'ant-design-vue';
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { updateMinAdjustPrice } from '../api';
|
||||
|
||||
defineOptions({
|
||||
name: 'MinPriceModal',
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const loading = ref(false);
|
||||
|
||||
const formData = ref<{
|
||||
id?: number;
|
||||
drugName?: string;
|
||||
currentMinPrice?: number | null;
|
||||
}>({});
|
||||
|
||||
const minPrice = ref<number | null>(null);
|
||||
|
||||
const [ModalComponent, modalApi] = useVbenModal({
|
||||
class: 'w-[500px]',
|
||||
onCancel() {
|
||||
modalApi.close();
|
||||
},
|
||||
onConfirm: async () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
if (!formData.value.id || minPrice.value === null) {
|
||||
message.error('参数不完整');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
await updateMinAdjustPrice({
|
||||
id: formData.value.id,
|
||||
min_adjust_price: minPrice.value,
|
||||
});
|
||||
message.success('设置成功');
|
||||
modalApi.close();
|
||||
const gridApi = modalApi.getData()?.gridApi;
|
||||
if (gridApi) {
|
||||
gridApi.reload();
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('设置失败:', error);
|
||||
message.error(error?.message || '设置失败,请重试');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
const data = modalApi.getData<typeof formData.value & { gridApi?: any }>();
|
||||
if (data) {
|
||||
formData.value = {
|
||||
id: data.id,
|
||||
drugName: data.drugName || data.drug_name,
|
||||
currentMinPrice: data.currentMinPrice || data.min_adjust_price || null,
|
||||
};
|
||||
minPrice.value = formData.value.currentMinPrice;
|
||||
}
|
||||
} else {
|
||||
formData.value = {};
|
||||
minPrice.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalComponent
|
||||
title="设置最低调整金额"
|
||||
:loading="loading"
|
||||
>
|
||||
<div v-if="formData.drugName" class="min-price-modal">
|
||||
<div class="mb-4">
|
||||
<p><strong>药品名称:</strong>{{ formData.drugName }}</p>
|
||||
<p v-if="formData.currentMinPrice" class="mt-2">
|
||||
<strong>当前最低调整金额:</strong>¥{{ Number(formData.currentMinPrice).toFixed(2) }}
|
||||
</p>
|
||||
<p v-else class="mt-2" style="color: #999">
|
||||
<strong>当前最低调整金额:</strong>未设置
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Form ref="formRef" :model="{ minPrice }" layout="vertical">
|
||||
<Form.Item
|
||||
label="最低调整金额"
|
||||
name="minPrice"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入最低调整金额' },
|
||||
{ type: 'number', min: 0, message: '金额不能小于0' },
|
||||
]"
|
||||
>
|
||||
<InputNumber
|
||||
v-model:value="minPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.01"
|
||||
style="width: 100%"
|
||||
placeholder="请输入最低调整金额(设置为0或留空表示不允许调整)"
|
||||
/>
|
||||
</Form.Item>
|
||||
<p class="text-gray-500 text-sm mt-2">
|
||||
提示:设置为0或留空表示该商品不允许调整价格
|
||||
</p>
|
||||
</Form>
|
||||
</div>
|
||||
</ModalComponent>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.min-price-modal {
|
||||
padding: 8px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -67,7 +67,13 @@ export const gridOptions: VxeGridProps<RowType> = {
|
||||
},
|
||||
{ field: 'status', title: '状态', slots: { default: 'status' } },
|
||||
{ field: 'created_at', title: '发布时间' },
|
||||
{ type: 'html', title: '操作', width: 230, slots: { default: 'action' } },
|
||||
{
|
||||
field: 'min_adjust_price',
|
||||
title: '最低调整金额',
|
||||
width: 130,
|
||||
slots: { default: 'min_adjust_price' },
|
||||
},
|
||||
{ type: 'html', title: '操作', width: 280, slots: { default: 'action' } },
|
||||
],
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
|
||||
@@ -15,6 +15,7 @@ import { deleteWesternMedicine, exportWesternMedicineApi } from './api';
|
||||
import FormModalDemo from './components/modal.vue';
|
||||
import ExcelUpload from './components/ExcelUpload.vue';
|
||||
import CategoryModal from './components/CategoryModal.vue';
|
||||
import MinPriceModal from './components/MinPriceModal.vue';
|
||||
import { formOptions } from './config/search';
|
||||
import { gridOptions } from './config/table';
|
||||
|
||||
@@ -51,6 +52,10 @@ const [CategoryModalComp, categoryModalApi] = useVbenModal({
|
||||
connectedComponent: CategoryModal,
|
||||
});
|
||||
|
||||
const [MinPriceModalComp, minPriceModalApi] = useVbenModal({
|
||||
connectedComponent: MinPriceModal,
|
||||
});
|
||||
|
||||
const openCategoryModal = (row: any) => {
|
||||
categoryModalApi.setData({
|
||||
row,
|
||||
@@ -59,6 +64,16 @@ const openCategoryModal = (row: any) => {
|
||||
categoryModalApi.open();
|
||||
};
|
||||
|
||||
const openMinPriceModal = (row: any) => {
|
||||
minPriceModalApi.setData({
|
||||
id: row.id,
|
||||
drugName: row.drug_name,
|
||||
min_adjust_price: row.min_adjust_price,
|
||||
gridApi,
|
||||
});
|
||||
minPriceModalApi.open();
|
||||
};
|
||||
|
||||
const showModal = (data = {}, isUpdate = false) => {
|
||||
formModalApi.setData({
|
||||
// 表单值
|
||||
@@ -106,6 +121,7 @@ const openExcelUploadModal = () => {
|
||||
<ExcelUploadModal />
|
||||
<FormModal />
|
||||
<CategoryModalComp />
|
||||
<MinPriceModalComp />
|
||||
<Grid>
|
||||
<template #toolbar-buttons>
|
||||
<TableAction
|
||||
@@ -184,6 +200,17 @@ const openExcelUploadModal = () => {
|
||||
<Tag :color="row.status_color">{{ row.status_txt }}</Tag>
|
||||
</template>
|
||||
<template #toolbar-tools></template>
|
||||
<template #min_adjust_price="{ row }">
|
||||
<a
|
||||
class="text-primary cursor-pointer hover:underline"
|
||||
@click="openMinPriceModal(row)"
|
||||
>
|
||||
<span v-if="row.min_adjust_price && row.min_adjust_price > 0">
|
||||
¥{{ Number(row.min_adjust_price).toFixed(2) }}
|
||||
</span>
|
||||
<span v-else style="color: #999">未设置</span>
|
||||
</a>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
@@ -195,6 +222,13 @@ const openExcelUploadModal = () => {
|
||||
// auth: ['western-medicine', 'sys:role:detail'],
|
||||
onClick: showModal.bind(null, row, true),
|
||||
},
|
||||
{
|
||||
label: '设置底价',
|
||||
type: 'link',
|
||||
icon: 'ant-design:dollar-outlined',
|
||||
size: 'small',
|
||||
onClick: openMinPriceModal.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
type: 'link',
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue';
|
||||
|
||||
import {Page, useVbenModal} from '@vben/common-ui';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import {
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
CheckCircleFilled,
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
Button,
|
||||
@@ -14,11 +13,10 @@ import {
|
||||
message,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Tooltip,
|
||||
Pagination,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
getDiseaseList,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
import { getDiseaseList } from '#/views/doctor/doctor-reception/api';
|
||||
import { debounce } from 'lodash-es';
|
||||
import {
|
||||
getDoctorMyDiseaseListApi,
|
||||
@@ -27,9 +25,22 @@ import {
|
||||
} from "#/views/doctor/settings/api";
|
||||
|
||||
const searchKey = ref('');
|
||||
const diagnosisList = ref([]);
|
||||
// 存储接口返回的所有诊断数据
|
||||
const allDiagnosisList = ref([]);
|
||||
const selectDiagnosisList = ref('');
|
||||
const setUpdateDiagnosis = ref();
|
||||
|
||||
// 分页相关状态
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(30); // 每页显示30条,tag比较小,可以多显示点
|
||||
|
||||
// 计算属性:当前页展示的诊断数据
|
||||
const paginatedDiagnosisList = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize.value;
|
||||
const end = start + pageSize.value;
|
||||
return allDiagnosisList.value.slice(start, end);
|
||||
});
|
||||
|
||||
/**
|
||||
* 常用诊断列表
|
||||
*/
|
||||
@@ -67,7 +78,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
setUpdateDiagnosis.value = isOpen ? modalApi.getData()?.updateDiagnosis : null;
|
||||
const {values} =
|
||||
const { values } =
|
||||
modalApi.getData<Record<string, any>>();
|
||||
if (values) {
|
||||
selectDiagnosisList.value = values;
|
||||
@@ -84,7 +95,8 @@ const [Modal, modalApi] = useVbenModal({
|
||||
*/
|
||||
function getDiagnosisList(searchKey = '') {
|
||||
getDiseaseList(searchKey).then((res) => {
|
||||
diagnosisList.value = res.map((item) => {
|
||||
// 处理所有数据
|
||||
const processedList = res.map((item) => {
|
||||
item.isSelect = 0;
|
||||
// 检查是否已在常用诊断中
|
||||
item.isInMyList = doctorMyDiseaseList.value.some(
|
||||
@@ -102,6 +114,10 @@ function getDiagnosisList(searchKey = '') {
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
// 更新总数据源并重置页码
|
||||
allDiagnosisList.value = processedList;
|
||||
currentPage.value = 1;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -160,8 +176,9 @@ async function removeFromMyDiagnosis(item) {
|
||||
message.success(`已将「${item.disease.name}」从常用诊断中移除`);
|
||||
// 刷新常用诊断列表
|
||||
await getDoctorMyDiseaseList();
|
||||
// 更新搜索结果中的状态
|
||||
const searchItem = diagnosisList.value.find(
|
||||
|
||||
// 更新总数据源中的状态 (不仅仅是当前页)
|
||||
const searchItem = allDiagnosisList.value.find(
|
||||
(d) => d.id === item.disease.id
|
||||
);
|
||||
if (searchItem) {
|
||||
@@ -171,86 +188,106 @@ async function removeFromMyDiagnosis(item) {
|
||||
message.error('删除失败,请重试');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页码改变
|
||||
*/
|
||||
function handlePageChange(page: number, size: number) {
|
||||
currentPage.value = page;
|
||||
pageSize.value = size;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[60%]" title="常用诊断">
|
||||
<Page>
|
||||
<Row :gutter="12">
|
||||
<Col :span="24">
|
||||
<InputSearch
|
||||
:model="searchKey"
|
||||
enter-button
|
||||
placeholder="请输想要搜索的内容..."
|
||||
@input="(e) => getDiagnosisListChage(e.target.value)"
|
||||
@search="getDiagnosisList"
|
||||
/>
|
||||
<Col :span="24" class="mb-4">
|
||||
<InputSearch :model="searchKey" allow-clear enter-button placeholder="请输入想要搜索的诊断名称..." size="large"
|
||||
@input="(e) => getDiagnosisListChage(e.target.value)" @search="getDiagnosisList" />
|
||||
</Col>
|
||||
|
||||
<Col :span="24">
|
||||
<!-- 常用诊断列表 -->
|
||||
<h1 class="diagnosis-section-title" v-if="doctorMyDiseaseList.length > 0">常用</h1>
|
||||
<div class="diagnosis-list">
|
||||
<div class="section-header" v-if="doctorMyDiseaseList.length > 0">
|
||||
<span class="section-title">常用诊断</span>
|
||||
<span class="section-count">{{ doctorMyDiseaseList.length }}</span>
|
||||
</div>
|
||||
|
||||
<div class="tag-container" v-if="doctorMyDiseaseList.length > 0">
|
||||
<div
|
||||
v-for="item in doctorMyDiseaseList"
|
||||
:key="item.id"
|
||||
class="diagnosis-item"
|
||||
class="smart-tag"
|
||||
:class="{ 'is-selected': item.disease.isSelect === 1 }"
|
||||
@click="selectDiagnosis(item.disease)"
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
:ghost="item.disease.isSelect !== 1"
|
||||
@click="selectDiagnosis(item.disease)"
|
||||
>
|
||||
{{ item.disease.name }}
|
||||
</Button>
|
||||
<div class="diagnosis-actions" @click.stop>
|
||||
<span class="tag-text">{{ item.disease.name }}</span>
|
||||
|
||||
<!-- Action Area -->
|
||||
<div class="tag-action-wrapper" @click.stop>
|
||||
<Popconfirm
|
||||
title="确定从常用诊断中移除吗?"
|
||||
placement="topRight"
|
||||
@confirm="removeFromMyDiagnosis(item)"
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
class="action-btn"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</Button>
|
||||
<Tooltip title="移除常用">
|
||||
<span class="action-icon delete">
|
||||
<DeleteOutlined />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索结果列表 -->
|
||||
<h1 class="diagnosis-section-title" v-if="diagnosisList.length > 0">搜索结果</h1>
|
||||
<div class="diagnosis-list">
|
||||
<div class="section-header mt-6" v-if="allDiagnosisList.length > 0">
|
||||
<span class="section-title">搜索结果</span>
|
||||
<span class="section-count">共 {{ allDiagnosisList.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<div class="tag-container">
|
||||
<div
|
||||
v-for="item in diagnosisList"
|
||||
v-for="item in paginatedDiagnosisList"
|
||||
:key="item.id"
|
||||
class="diagnosis-item"
|
||||
class="smart-tag"
|
||||
:class="{ 'is-selected': item.isSelect === 1 }"
|
||||
@click="selectDiagnosis(item)"
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
:ghost="item.isSelect !== 1"
|
||||
@click="selectDiagnosis(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</Button>
|
||||
<div class="diagnosis-actions" @click.stop>
|
||||
<Button
|
||||
v-if="!item.isInMyList"
|
||||
type="text"
|
||||
size="small"
|
||||
class="action-btn add-btn"
|
||||
@click="addToMyDiagnosis(item)"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
<span class="action-text">加入常用</span>
|
||||
</Button>
|
||||
<span v-else class="already-added">已添加</span>
|
||||
<span class="tag-text">{{ item.name }}</span>
|
||||
|
||||
<div class="tag-action-wrapper" @click.stop>
|
||||
<Tooltip v-if="!item.isInMyList" title="加入常用">
|
||||
<span class="action-icon add" @click="addToMyDiagnosis(item)">
|
||||
<PlusOutlined />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip v-else title="已在常用列表">
|
||||
<span class="action-icon added">
|
||||
<CheckCircleFilled />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="allDiagnosisList.length === 0 && searchKey" class="empty-state">
|
||||
暂无相关诊断
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div v-if="allDiagnosisList.length > 0" class="pagination-wrapper">
|
||||
<Pagination
|
||||
v-model:current="currentPage"
|
||||
v-model:pageSize="pageSize"
|
||||
:total="allDiagnosisList.length"
|
||||
:show-total="total => `共 ${total} 条`"
|
||||
show-size-changer
|
||||
show-quick-jumper
|
||||
size="small"
|
||||
@change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -259,80 +296,208 @@ async function removeFromMyDiagnosis(item) {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.diagnosis-section-title {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
/* Section Headers */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
background-color: #3b82f6;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-count {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
background: #f3f4f6;
|
||||
padding: 1px 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .diagnosis-section-title {
|
||||
color: #e5e7eb;
|
||||
.dark .section-header {
|
||||
.section-title {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
.section-count {
|
||||
background: #374151;
|
||||
color: #9ca3af;
|
||||
}
|
||||
}
|
||||
|
||||
.diagnosis-list {
|
||||
/* Tag Container */
|
||||
.tag-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
min-height: 40px;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.diagnosis-item {
|
||||
/* Smart Tag Styles */
|
||||
.smart-tag {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 4px 0;
|
||||
|
||||
&:hover .diagnosis-actions {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.diagnosis-actions {
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: -8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.dark .diagnosis-actions {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
color: #1890ff;
|
||||
padding: 6px 14px;
|
||||
background-color: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 9999px; /* Pill shape */
|
||||
cursor: pointer;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
user-select: none;
|
||||
max-width: 100%;
|
||||
|
||||
&:hover {
|
||||
color: #40a9ff;
|
||||
background: rgba(24, 144, 255, 0.1);
|
||||
background-color: #f3f4f6;
|
||||
border-color: #d1d5db;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
|
||||
.tag-action-wrapper {
|
||||
max-width: 24px;
|
||||
margin-left: 8px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Selected State */
|
||||
&.is-selected {
|
||||
background-color: #eff6ff;
|
||||
border-color: #3b82f6;
|
||||
color: #2563eb;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
background-color: #dbeafe;
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
color: #2563eb;
|
||||
}
|
||||
}
|
||||
|
||||
/* Text Styling */
|
||||
.tag-text {
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
.action-text {
|
||||
margin-left: 2px;
|
||||
.dark .smart-tag {
|
||||
background-color: #1f2937;
|
||||
border-color: #374151;
|
||||
|
||||
.tag-text {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #374151;
|
||||
border-color: #4b5563;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
background-color: rgba(59, 130, 246, 0.15);
|
||||
border-color: #3b82f6;
|
||||
|
||||
.tag-text {
|
||||
color: #60a5fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.already-added {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
padding: 2px 6px;
|
||||
/* Action Area (The Delete/Add buttons) */
|
||||
.tag-action-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
margin-left: 0;
|
||||
|
||||
/* Show existing "Added" checkmark always or control via props?
|
||||
Here we keep it hidden until hover for cleaner look,
|
||||
OR we can make "Added" status distinct.
|
||||
Let's keep the slide-in effect for actions. */
|
||||
}
|
||||
|
||||
/* Specifically for "Already Added" checkmark - maybe always show it?
|
||||
To keep UI clean, let's show it on hover or if it's "isInMyList"
|
||||
but the requirement is to optimize hover. */
|
||||
.action-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.delete {
|
||||
color: #9ca3af;
|
||||
&:hover {
|
||||
background-color: #fee2e2;
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
&.add {
|
||||
color: #9ca3af;
|
||||
&:hover {
|
||||
background-color: #dbeafe;
|
||||
color: #3b82f6;
|
||||
}
|
||||
}
|
||||
|
||||
&.added {
|
||||
color: #10b981;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #9ca3af;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed #e5e7eb;
|
||||
}
|
||||
|
||||
.dark .pagination-wrapper {
|
||||
border-color: #374151;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue';
|
||||
|
||||
import {Page, useVbenModal} from '@vben/common-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import {
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
EnterOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
Button,
|
||||
@@ -14,11 +13,10 @@ import {
|
||||
message,
|
||||
Popconfirm,
|
||||
Row,
|
||||
Tooltip,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
getDoctorOrderList,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
import { getDoctorOrderList } from '#/views/doctor/doctor-reception/api';
|
||||
import {
|
||||
createDoctorOrderApi,
|
||||
deleteDoctorOrderApi,
|
||||
@@ -48,7 +46,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
setUpdateDoctorOrder.value = isOpen ? modalApi.getData()?.updateDoctorOrder : null;
|
||||
const {values} =
|
||||
const { values } =
|
||||
modalApi.getData<Record<string, any>>();
|
||||
if (values) {
|
||||
selectDiagnosisList.value = values;
|
||||
@@ -183,60 +181,56 @@ function cancelAdd() {
|
||||
<Page>
|
||||
<Row :gutter="12">
|
||||
<!-- 我的医嘱 -->
|
||||
<Col :span="24">
|
||||
<h1 class="order-section-title">我的医嘱</h1>
|
||||
<div class="order-list">
|
||||
<Col :span="24" class="mb-4">
|
||||
<div class="section-header">
|
||||
<span class="section-title">我的医嘱</span>
|
||||
</div>
|
||||
|
||||
<div class="tag-container">
|
||||
<div
|
||||
v-for="item in doctorOrderMyList"
|
||||
:key="item.id"
|
||||
class="order-item"
|
||||
class="smart-tag"
|
||||
:class="{ 'is-selected': item.isSelect === 1 }"
|
||||
@click="selectDoctorOrder(item, 1)"
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
:ghost="item.isSelect !== 1"
|
||||
@click="selectDoctorOrder(item, 1)"
|
||||
>
|
||||
{{ item.content }}
|
||||
</Button>
|
||||
<div class="order-actions" @click.stop>
|
||||
<span class="tag-text">{{ item.content }}</span>
|
||||
|
||||
<!-- Action Area -->
|
||||
<div class="tag-action-wrapper" @click.stop>
|
||||
<Popconfirm
|
||||
title="确定删除这条医嘱吗?"
|
||||
placement="topRight"
|
||||
@confirm="deleteMyDoctorOrder(item)"
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
class="action-btn"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</Button>
|
||||
<Tooltip title="删除医嘱">
|
||||
<span class="action-icon delete">
|
||||
<DeleteOutlined />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加医嘱按钮/输入框 -->
|
||||
<div
|
||||
v-if="!showAddInput"
|
||||
class="add-order-btn"
|
||||
@click="showAddInput = true"
|
||||
>
|
||||
<PlusOutlined />
|
||||
<span>添加医嘱</span>
|
||||
</div>
|
||||
<!-- Add New Button/Input Area -->
|
||||
<div class="add-tag-wrapper" :class="{ 'is-input': showAddInput }">
|
||||
<div v-if="!showAddInput" class="add-btn-tag" @click="showAddInput = true">
|
||||
<PlusOutlined class="mr-1"/> 添加医嘱
|
||||
</div>
|
||||
|
||||
<div v-else class="add-order-input">
|
||||
<Input
|
||||
v-model:value="newOrderContent"
|
||||
placeholder="输入医嘱内容"
|
||||
@pressEnter="addMyDoctorOrder"
|
||||
/>
|
||||
<div class="add-actions">
|
||||
<Button size="small" @click="cancelAdd">取消</Button>
|
||||
<Button type="primary" size="small" @click="addMyDoctorOrder">
|
||||
添加
|
||||
</Button>
|
||||
<div v-else class="add-input-container">
|
||||
<Input
|
||||
v-model:value="newOrderContent"
|
||||
placeholder="输入内容后回车"
|
||||
size="small"
|
||||
class="custom-input"
|
||||
ref="inputRef"
|
||||
@pressEnter="addMyDoctorOrder"
|
||||
@blur="!newOrderContent && cancelAdd()"
|
||||
/>
|
||||
<span class="enter-icon" @click="addMyDoctorOrder">
|
||||
<EnterOutlined />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -244,21 +238,19 @@ function cancelAdd() {
|
||||
|
||||
<!-- 公共医嘱 -->
|
||||
<Col :span="24">
|
||||
<h1 class="order-section-title" v-if="doctorOrderCommonList.length > 0">公共医嘱</h1>
|
||||
<div class="order-list">
|
||||
<div class="section-header" v-if="doctorOrderCommonList.length > 0">
|
||||
<span class="section-title">公共医嘱</span>
|
||||
</div>
|
||||
|
||||
<div class="tag-container">
|
||||
<div
|
||||
v-for="item in doctorOrderCommonList"
|
||||
:key="item.id"
|
||||
class="order-item"
|
||||
class="smart-tag"
|
||||
:class="{ 'is-selected': item.isSelect === 1 }"
|
||||
@click="selectDoctorOrder(item, 2)"
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
:ghost="item.isSelect !== 1"
|
||||
@click="selectDoctorOrder(item, 2)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</Button>
|
||||
<span class="tag-text">{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
@@ -268,122 +260,203 @@ function cancelAdd() {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-section-title {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
/* Common Headers */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
position: relative;
|
||||
padding-left: 10px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
background-color: #3b82f6;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark .order-section-title {
|
||||
.dark .section-header .section-title {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
.order-list {
|
||||
/* Tag Layout */
|
||||
.tag-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.order-item {
|
||||
/* Modern Tag Style */
|
||||
.smart-tag {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 4px 0;
|
||||
|
||||
&:hover .order-actions {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.order-actions {
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: -8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.dark .order-actions {
|
||||
background: #374151;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.add-order-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 12px;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
padding: 6px 14px;
|
||||
background-color: #f9fafb;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
transition: all 0.3s;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
user-select: none;
|
||||
height: 34px;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
color: #1890ff;
|
||||
background-color: #f3f4f6;
|
||||
border-color: #d1d5db;
|
||||
transform: translateY(-1px);
|
||||
|
||||
.tag-action-wrapper {
|
||||
max-width: 24px;
|
||||
margin-left: 6px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
background-color: #eff6ff;
|
||||
border-color: #3b82f6;
|
||||
color: #2563eb;
|
||||
|
||||
.tag-text {
|
||||
color: #2563eb;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .add-order-btn {
|
||||
.dark .smart-tag {
|
||||
background-color: #1f2937;
|
||||
border-color: #374151;
|
||||
.tag-text { color: #d1d5db; }
|
||||
|
||||
&:hover {
|
||||
background-color: #374151;
|
||||
border-color: #4b5563;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
background-color: rgba(59, 130, 246, 0.15);
|
||||
border-color: #3b82f6;
|
||||
.tag-text { color: #60a5fa; }
|
||||
}
|
||||
}
|
||||
|
||||
/* Action Icon slide-in logic */
|
||||
.tag-action-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
transition: all 0.25s ease;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
|
||||
&.delete {
|
||||
color: #9ca3af;
|
||||
&:hover {
|
||||
background-color: #fee2e2;
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add New Tag Button Area */
|
||||
.add-tag-wrapper {
|
||||
height: 34px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.is-input {
|
||||
width: 200px; /* Expand width when typing */
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
height: 100%;
|
||||
border: 1px dashed #d1d5db;
|
||||
border-radius: 9999px;
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #3b82f6;
|
||||
color: #3b82f6;
|
||||
background-color: rgba(59, 130, 246, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.dark .add-btn-tag {
|
||||
border-color: #4b5563;
|
||||
color: #9ca3af;
|
||||
|
||||
&:hover {
|
||||
border-color: #3b82f6;
|
||||
color: #3b82f6;
|
||||
}
|
||||
}
|
||||
|
||||
.add-order-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px;
|
||||
border: 1px solid #1890ff;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
.add-input-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
:deep(.ant-input) {
|
||||
flex: 1;
|
||||
border: none;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
.custom-input {
|
||||
height: 100%;
|
||||
border-radius: 9999px;
|
||||
padding-right: 30px; /* Space for enter icon */
|
||||
font-size: 13px;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.enter-icon {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
padding: 2px;
|
||||
|
||||
&:hover {
|
||||
color: #3b82f6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark .add-order-input {
|
||||
background: #374151;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.add-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,8 +6,10 @@ import { useUserStore } from '@vben/stores';
|
||||
|
||||
import {
|
||||
ContainerOutlined,
|
||||
DeleteOutlined,
|
||||
MinusOutlined,
|
||||
PlusOutlined,
|
||||
ShoppingCartOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
Badge,
|
||||
@@ -23,6 +25,7 @@ import {
|
||||
Row,
|
||||
Select,
|
||||
SelectOption,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
@@ -118,7 +121,13 @@ const getDrugListByWesternModal = debounce(async (searchText = '') => {
|
||||
select_number: matched?.select_number || 0,
|
||||
drug: {
|
||||
...item.drug,
|
||||
// 如果已选,使用已选的数量,否则默认为1
|
||||
number: matched?.number || 1,
|
||||
// 如果已选,同步已选的用法
|
||||
frequency_id: matched?.frequency_id || item.drug.frequency_id,
|
||||
type_id: matched?.type_id || item.drug.type_id,
|
||||
time_id: matched?.time_id || item.drug.time_id,
|
||||
unit_id: matched?.unit_id || item.drug.unit_id,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -294,6 +303,34 @@ function propProducts(data) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接设置商品数量 (用于输入框)
|
||||
* @param {object} data - 商品对象
|
||||
* @param {number} value - 新数量
|
||||
*/
|
||||
function setProductQuantity(data, value) {
|
||||
if (value < 0) return;
|
||||
if (value === 0) {
|
||||
propProducts(data); // 如果设为0,走删除逻辑
|
||||
return;
|
||||
}
|
||||
|
||||
const existItem = selectList.value.find((item) => item.index_id === data.id);
|
||||
if (existItem) {
|
||||
selectList.value = selectList.value.map((item) =>
|
||||
item.index_id === data.id ? { ...item, select_number: value } : item,
|
||||
);
|
||||
syncDrugList(data.id, value);
|
||||
updateSelectStorage();
|
||||
} else {
|
||||
// 如果还没添加,先添加一次,然后再设置数量
|
||||
addProducts(data);
|
||||
// 这里因为addProducts是异步或者状态更新可能滞后,简单处理为再次调用以修正数量,
|
||||
// 或者更严谨地构造新对象。鉴于addProducts逻辑较重,这里假设用户是先点加号再改数量。
|
||||
// 如果用户直接在输入框输入数字(目前UI没开放未选中的输入框),需要额外处理。
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新本地存储中的选择列表
|
||||
*/
|
||||
@@ -493,7 +530,7 @@ function selectUnitChange(id) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新药品数量
|
||||
* 更新药品数量 (单次剂量)
|
||||
* @param {number} id - 药品ID
|
||||
* @param {number} number - 新的数量
|
||||
*/
|
||||
@@ -534,47 +571,177 @@ function updateProductNumber(id, number) {
|
||||
<Page>
|
||||
<Row :gutter="12">
|
||||
<!-- 搜索框 -->
|
||||
<Col :span="24">
|
||||
<Col :span="24" class="mb-4">
|
||||
<InputSearch
|
||||
v-model:value="searchKey"
|
||||
enter-button
|
||||
placeholder="请输入商品名称或拼音首拼"
|
||||
size="large"
|
||||
@input="(e) => getDrugListByWesternModal(e.target.value)"
|
||||
@search="getDrugListByWesternModal"
|
||||
/>
|
||||
</Col>
|
||||
<!-- 药品列表 -->
|
||||
<Col v-for="item in drugList" :key="item.id" :span="6">
|
||||
<Badge :count="item.select_number" class="mt-5">
|
||||
<!-- 中药卡片 -->
|
||||
<Card v-if="type === 1" hoverable>
|
||||
<template #actions>
|
||||
<Button
|
||||
v-if="item.select_number > 0"
|
||||
key="prop"
|
||||
type="link"
|
||||
@click="propProducts(item)"
|
||||
>
|
||||
从清单移除
|
||||
</Button>
|
||||
<Button v-else key="add" type="link" @click="addProducts(item)">
|
||||
添加到清单
|
||||
</Button>
|
||||
</template>
|
||||
<CardMeta
|
||||
:description="item.drug?.pinyin_simple"
|
||||
:title="item.drug?.drug_name"
|
||||
>
|
||||
<template #avatar>
|
||||
<a-avatar src="https://joeschmoe.io/api/v1/random" />
|
||||
</template>
|
||||
</CardMeta>
|
||||
<div class="card-box mt-5" style="padding: 10px 5px">
|
||||
<p>单价:{{ item.price }}/g</p>
|
||||
<!-- 选择中药煎法 -->
|
||||
|
||||
<!-- 西药列表视图 (参考EditModal) -->
|
||||
<Col :span="24" v-if="type === 2">
|
||||
<div v-if="drugList.length === 0" class="text-center py-8 text-gray-400 bg-gray-50 rounded-lg border border-dashed border-gray-200">
|
||||
暂无药品,请搜索添加
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-2">
|
||||
<Card
|
||||
v-for="item in drugList"
|
||||
:key="item.id"
|
||||
size="small"
|
||||
class="drug-row-card"
|
||||
:class="{ 'is-selected': item.select_number > 0 }"
|
||||
hoverable
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- 1. 图片区 -->
|
||||
<div class="w-12 h-12 flex-shrink-0 cursor-pointer relative group" @click="setVisible(true, item.drug.instruction)">
|
||||
<Image
|
||||
v-if="item.drug?.image"
|
||||
:src="item.drug.image"
|
||||
:width="48"
|
||||
:height="48"
|
||||
class="rounded object-cover"
|
||||
:preview="false"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="w-12 h-12 bg-gray-100 rounded flex items-center justify-center text-gray-400 text-xs"
|
||||
>
|
||||
无图
|
||||
</div>
|
||||
<!-- 说明书图标遮罩 -->
|
||||
<div class="absolute inset-0 bg-black/40 hidden group-hover:flex items-center justify-center rounded transition-all">
|
||||
<ContainerOutlined class="text-white" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 信息区 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="font-bold text-gray-800 truncate" :title="item.drug?.drug_name">{{ item.drug?.drug_name }}</span>
|
||||
<Tag v-if="item.select_number > 0" color="blue" class="m-0 text-xs scale-90">已选</Tag>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 truncate mb-1" :title="item.drug?.function">
|
||||
{{ item.drug?.pinyin_simple }}
|
||||
</div>
|
||||
<div class="text-red-500 font-medium text-sm">
|
||||
¥{{ item.price }} <span class="text-gray-400 text-xs font-normal">/ {{ item.drug?.unit?.name || '单位' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 用法选择区 (仅当选中或一直显示但未选中时置灰) -->
|
||||
<div class="flex gap-2">
|
||||
<!-- 用法 -->
|
||||
<Select
|
||||
v-model:value="item.drug.type_id"
|
||||
placeholder="用法"
|
||||
style="width: 90px"
|
||||
size="small"
|
||||
@change="selectTypeChange"
|
||||
@dropdown-visible-change="selectProductChange(item.id)"
|
||||
>
|
||||
<SelectOption v-for="val in drugUseType" :key="val.id" :value="val.id">{{ val.name }}</SelectOption>
|
||||
</Select>
|
||||
|
||||
<!-- 频率 -->
|
||||
<Select
|
||||
v-model:value="item.drug.frequency_id"
|
||||
placeholder="频率"
|
||||
style="width: 100px"
|
||||
size="small"
|
||||
@change="selectFrequencyChange"
|
||||
@dropdown-visible-change="selectProductChange(item.id)"
|
||||
>
|
||||
<SelectOption v-for="val in drugUseFrequency" :key="val.id" :value="val.id">{{ val.name }}</SelectOption>
|
||||
</Select>
|
||||
|
||||
<!-- 时间 (可选) -->
|
||||
<Select
|
||||
v-model:value="item.drug.time_id"
|
||||
placeholder="时间"
|
||||
style="width: 90px"
|
||||
size="small"
|
||||
@change="selectTimeChange"
|
||||
@dropdown-visible-change="selectProductChange(item.id)"
|
||||
>
|
||||
<SelectOption v-for="val in drugTime" :key="val.id" :value="val.id">{{ val.name }}</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<!-- 4. 单次剂量 -->
|
||||
<div class="flex items-center gap-1 bg-gray-50 p-1 rounded border border-gray-100">
|
||||
<span class="text-xs text-gray-400 px-1">每次</span>
|
||||
<InputNumber
|
||||
v-model:value="item.drug.number"
|
||||
:min="1"
|
||||
:max="100"
|
||||
size="small"
|
||||
style="width: 50px"
|
||||
:controls="false"
|
||||
class="text-center-input"
|
||||
@change="updateProductNumber(item.id, item.drug.number)"
|
||||
/>
|
||||
<Select
|
||||
v-model:value="item.drug.unit_id"
|
||||
class="no-border-select"
|
||||
style="width: 50px"
|
||||
size="small"
|
||||
@change="selectUnitChange"
|
||||
@dropdown-visible-change="selectProductChange(item.id)"
|
||||
>
|
||||
<SelectOption v-for="val in drugUnit" :key="val.id" :value="val.id">{{ val.name }}</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<!-- 5. 数量操作区 -->
|
||||
<div class="flex items-center gap-2 pl-2 border-l border-gray-100">
|
||||
<template v-if="item.select_number > 0">
|
||||
<Button size="small" shape="circle" @click="propProducts(item)">
|
||||
<template #icon><MinusOutlined class="text-xs" /></template>
|
||||
</Button>
|
||||
<span class="font-bold w-6 text-center text-primary">{{ item.select_number }}</span>
|
||||
<Button size="small" shape="circle" @click="addProducts(item)">
|
||||
<template #icon><PlusOutlined class="text-xs" /></template>
|
||||
</Button>
|
||||
<Button type="text" danger size="small" @click="propProducts({ ...item, select_number: 1 })">
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</template>
|
||||
<Button v-else type="primary" size="small" ghost @click="addProducts(item)">
|
||||
<template #icon><ShoppingCartOutlined /></template>
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<!-- 中药列表视图 (保持 Grid 但优化样式) -->
|
||||
<Col v-else-if="type === 1" v-for="item in drugList" :key="item.id" :span="6">
|
||||
<Badge :count="item.select_number" class="w-full mt-4">
|
||||
<Card hoverable size="small" class="chinese-drug-grid-card" :class="{ 'is-selected': item.select_number > 0 }">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<div class="font-bold text-base text-gray-800">{{ item.drug?.drug_name }}</div>
|
||||
<div class="text-xs text-gray-400 mt-1">{{ item.drug?.pinyin_simple }}</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-red-500">¥{{ item.price }}</div>
|
||||
<div class="text-xs text-gray-400">/g</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 煎法选择 -->
|
||||
<Select
|
||||
:value="item.drug?.way_id"
|
||||
class="mt-3 w-full"
|
||||
class="w-full"
|
||||
size="small"
|
||||
placeholder="煎服"
|
||||
@change="selectDrugUseWayChange"
|
||||
@dropdown-visible-change="selectProductChange(item.id)"
|
||||
@@ -588,174 +755,30 @@ function updateProductNumber(id, number) {
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<div class="mt-3">
|
||||
<!-- 数量输入框 -->
|
||||
<InputNumber
|
||||
v-model:value="item.drug.number"
|
||||
class="w-3/4"
|
||||
@change="updateProductNumber(item.id, item.drug.number)"
|
||||
>
|
||||
<template #addonBefore>
|
||||
<MinusOutlined
|
||||
key="prop"
|
||||
@click="
|
||||
updateProductNumber(
|
||||
item.id,
|
||||
item.drug.number > 1 ? item.drug.number - 1 : 0,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<PlusOutlined
|
||||
key="add"
|
||||
@click="
|
||||
updateProductNumber(
|
||||
item.id,
|
||||
item.drug.number < 1000
|
||||
? item.drug.number + 1
|
||||
: 1000,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</InputNumber>
|
||||
<!-- 单位选择 -->
|
||||
<Select
|
||||
:value="item.drug?.unit_id"
|
||||
class="w-1/4"
|
||||
disabled
|
||||
@change="selectUnitChange"
|
||||
@dropdown-visible-change="selectProductChange(item.id)"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugUnit"
|
||||
:key="index"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<!-- 西药卡片 -->
|
||||
<Card v-else-if="type === 2" hoverable>
|
||||
<template #cover>
|
||||
<Image :height="200" :src="item.drug?.image" />
|
||||
</template>
|
||||
<template #actions>
|
||||
<MinusOutlined key="prop" @click="propProducts(item)" />
|
||||
<ContainerOutlined
|
||||
@click="setVisible(true, item.drug.instruction)"
|
||||
/>
|
||||
<PlusOutlined key="add" @click="addProducts(item)" />
|
||||
</template>
|
||||
<CardMeta
|
||||
:description="item.drug?.function"
|
||||
:title="`${item.drug?.drug_name}(${item.drug?.pinyin_simple})`"
|
||||
>
|
||||
<template #avatar>
|
||||
<a-avatar src="https://joeschmoe.io/api/v1/random" />
|
||||
</template>
|
||||
</CardMeta>
|
||||
<div class="card-box mt-5" style="padding: 10px 5px">
|
||||
<Popover>
|
||||
<!-- <p class="drug-function text-xs">-->
|
||||
<!-- 功效:{{ item.drug?.function }}-->
|
||||
<!-- </p>-->
|
||||
<template #content>
|
||||
<p>功效:{{ item.drug?.function }}</p>
|
||||
<p>用法:{{ item.drug?.usage }}</p>
|
||||
</template>
|
||||
</Popover>
|
||||
<p class="text-source mt-5">{{ item.drug?.source }}</p>
|
||||
<!-- 选择频次 -->
|
||||
<Select
|
||||
v-model:value="item.drug.frequency_id"
|
||||
class="mt-3 w-full"
|
||||
@change="selectFrequencyChange"
|
||||
@dropdown-visible-change="selectProductChange(item.id)"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugUseFrequency"
|
||||
:key="index"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<!-- 选择时间 -->
|
||||
<Select
|
||||
v-model:value="item.drug.time_id"
|
||||
class="mt-3 w-1/2"
|
||||
@change="selectTimeChange"
|
||||
@dropdown-visible-change="selectProductChange(item.id)"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugTime"
|
||||
:key="index"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<!-- 选择使用方法 -->
|
||||
<Select
|
||||
v-model:value="item.drug.type_id"
|
||||
class="mt-3 w-1/2"
|
||||
@change="selectTypeChange"
|
||||
@dropdown-visible-change="selectProductChange(item.id)"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugUseType"
|
||||
:key="index"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
<div class="mt-3">
|
||||
<!-- 数量输入框 -->
|
||||
<InputNumber v-model:value="item.drug.number" class="w-3/5">
|
||||
<template #addonBefore>
|
||||
<MinusOutlined
|
||||
key="prop"
|
||||
@click="
|
||||
updateProductNumber(
|
||||
item.id,
|
||||
item.drug.number > 1 ? item.drug.number - 1 : 0,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<PlusOutlined
|
||||
key="add"
|
||||
@click="
|
||||
updateProductNumber(
|
||||
item.id,
|
||||
item.drug.number < 100 ? item.drug.number + 1 : 100,
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</InputNumber>
|
||||
<!-- 单位选择 -->
|
||||
<Select
|
||||
v-model:value="item.drug.unit_id"
|
||||
class="w-2/5"
|
||||
@change="selectUnitChange"
|
||||
@dropdown-visible-change="selectProductChange(item.id)"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugUnit"
|
||||
:key="index"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
|
||||
<!-- 数量控制 -->
|
||||
<div class="flex items-center justify-between mt-1">
|
||||
<!-- 数量输入 (dose) -->
|
||||
<div class="flex items-center border rounded px-1 flex-1 mr-2 bg-gray-50">
|
||||
<InputNumber
|
||||
v-model:value="item.drug.number"
|
||||
:controls="false"
|
||||
size="small"
|
||||
class="flex-1 !bg-transparent !border-0 shadow-none text-center"
|
||||
@change="updateProductNumber(item.id, item.drug.number)"
|
||||
/>
|
||||
<span class="text-gray-400 text-xs px-1">g</span>
|
||||
</div>
|
||||
|
||||
<!-- 加减按钮 (Action) -->
|
||||
<div class="flex items-center gap-1">
|
||||
<Button v-if="item.select_number > 0" size="small" type="text" danger class="!px-1" @click="propProducts(item)">
|
||||
移除
|
||||
</Button>
|
||||
<Button v-else size="small" type="primary" ghost class="!px-2" @click="addProducts(item)">
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -766,7 +789,49 @@ function updateProductNumber(id, number) {
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.drug-row-card {
|
||||
transition: all 0.2s;
|
||||
border: 1px solid #f0f0f0;
|
||||
|
||||
&.is-selected {
|
||||
border-color: #3b82f6;
|
||||
background-color: #f0f9ff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.chinese-drug-grid-card {
|
||||
transition: all 0.2s;
|
||||
&.is-selected {
|
||||
border-color: #10b981;
|
||||
background-color: #ecfdf5;
|
||||
}
|
||||
}
|
||||
|
||||
.text-center-input {
|
||||
text-align: center;
|
||||
:deep(input) {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.no-border-select {
|
||||
:deep(.ant-select-selector) {
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.drug-function {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
|
||||
@@ -100,3 +100,14 @@ export async function getRegisterStoreInfo(registerId: number) {
|
||||
params: { register_id: registerId }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据挂号ID获取药品信息
|
||||
* @param registerId 挂号ID
|
||||
* @param storeId 门店ID
|
||||
*/
|
||||
export async function getDrugsByRegisterId(registerId: number, storeId: number) {
|
||||
return requestClient.get<any>(`${prefix}get-drugs-by-register-id`, {
|
||||
params: { register_id: registerId, store_id: storeId }
|
||||
});
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import { Button, message } from 'ant-design-vue';
|
||||
import { usePrescriptionStore } from '#/store/prescription';
|
||||
import { getProductListDoctorReception } from '#/views/doctor/doctor-reception/api';
|
||||
import { getDrugsByRegisterId } from '../api/index.ts';
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
@@ -40,58 +40,47 @@ const hasUsedDrugText = computed(() => {
|
||||
|
||||
// 添加药品到处方单
|
||||
const handleAddToPrescription = async () => {
|
||||
if (!experienceData.value.drug_name) {
|
||||
message.warning('该就诊经历中没有药品信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查处方store是否已初始化
|
||||
if (!prescriptionStore.currentRegisterId) {
|
||||
message.warning('请先接诊患者');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取门店ID
|
||||
let storeId = prescriptionStore.myStoreId;
|
||||
if (!storeId || storeId === 0) {
|
||||
if (prescriptionStore.myStoreList && prescriptionStore.myStoreList.length > 0) {
|
||||
storeId = prescriptionStore.myStoreList[0].id;
|
||||
} else {
|
||||
message.warning('请先选择诊所');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addingDrug.value = true;
|
||||
try {
|
||||
// 通过药品名称搜索药品
|
||||
// 如果myStoreId为0,尝试使用myStoreList的第一个
|
||||
let storeId = prescriptionStore.myStoreId;
|
||||
if (!storeId || storeId === 0) {
|
||||
if (prescriptionStore.myStoreList && prescriptionStore.myStoreList.length > 0) {
|
||||
storeId = prescriptionStore.myStoreList[0].id;
|
||||
} else {
|
||||
message.warning('请先选择诊所');
|
||||
return;
|
||||
// 根据 register_id 获取药品信息
|
||||
const res = await getDrugsByRegisterId(prescriptionStore.currentRegisterId, storeId);
|
||||
const drugList = res?.result || res?.data || res || [];
|
||||
|
||||
if (!drugList || drugList.length === 0) {
|
||||
message.warning('未找到药品信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历药品列表,添加到处方单
|
||||
let addedCount = 0;
|
||||
for (const drugData of drugList) {
|
||||
const success = prescriptionStore.addProducts(drugData);
|
||||
if (success) {
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await getProductListDoctorReception({
|
||||
store_id: storeId,
|
||||
type: 2, // 中成(西)药
|
||||
name: experienceData.value.drug_name,
|
||||
});
|
||||
|
||||
if (!res || res.length === 0) {
|
||||
message.warning(`未找到药品:${experienceData.value.drug_name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用第一个匹配的药品
|
||||
const drugData = res[0];
|
||||
|
||||
// 检查药品是否已在处方中
|
||||
const existItem = prescriptionStore.currentDrugs.find(
|
||||
(item) => item.index_id === drugData.id,
|
||||
);
|
||||
if (existItem) {
|
||||
message.warning('该药品已在处方中');
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加药品到处方单
|
||||
const success = prescriptionStore.addProducts(drugData);
|
||||
if (success) {
|
||||
message.success(`已将${drugData.drug.drug_name}添加到处方单`);
|
||||
if (addedCount > 0) {
|
||||
message.success(`已成功添加 ${addedCount} 个药品到处方单`);
|
||||
} else {
|
||||
message.warning('所有药品已在处方中');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加药品失败:', error);
|
||||
|
||||
@@ -19,7 +19,7 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['reception-success', 'enter-chat']);
|
||||
const emit = defineEmits(['reception-success', 'enter-chat', 'refresh-list']);
|
||||
|
||||
const patientDetail = ref(null);
|
||||
const loading = ref(false);
|
||||
@@ -82,6 +82,7 @@ const handleReception = async () => {
|
||||
status: 2, // 接诊中
|
||||
};
|
||||
emit('reception-success', receptionResult);
|
||||
emit('refresh-list'); // 通知父组件刷新列表
|
||||
await fetchPatientDetail();
|
||||
} catch (error) {
|
||||
console.error('接诊失败:', error);
|
||||
|
||||
@@ -215,6 +215,12 @@ const handleReceptionSuccess = (patient) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理刷新列表
|
||||
const handleRefreshList = () => {
|
||||
// 刷新患者列表
|
||||
patientListRef.value?.fetchPatientList();
|
||||
};
|
||||
|
||||
// 结束接诊
|
||||
const handleEndConsultation = () => {
|
||||
if (!selectedPatient.value) {
|
||||
@@ -506,6 +512,7 @@ const handleAddPatientProductsToPrescription = async () => {
|
||||
:doctor-id="userStore.currentUser?.doctor_id"
|
||||
@reception-success="handleReceptionSuccess"
|
||||
@enter-chat="handleEnterChat"
|
||||
@refresh-list="handleRefreshList"
|
||||
class="patient-info-panel"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -101,3 +101,37 @@ export async function getRegisterStoreInfo(registerId: number) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据挂号ID获取药品信息
|
||||
* @param registerId 挂号ID
|
||||
* @param storeId 门店ID
|
||||
*/
|
||||
export async function getDrugsByRegisterId(registerId: number, storeId: number) {
|
||||
return requestClient.get<any>(`${prefix}get-drugs-by-register-id`, {
|
||||
params: { register_id: registerId, store_id: storeId }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取患者订单列表
|
||||
* @param registerId 挂号ID
|
||||
*/
|
||||
export async function getPatientOrders(registerId: number) {
|
||||
return requestClient.get<any>(`${prefix}get-patient-orders`, {
|
||||
params: { register_id: registerId }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整订单商品价格
|
||||
* @param data 调整参数
|
||||
*/
|
||||
export async function adjustOrderItemPrice(data: {
|
||||
product_order_id: number;
|
||||
product_order_item_id: number;
|
||||
new_price: number;
|
||||
register_id: number;
|
||||
remark?: string;
|
||||
}) {
|
||||
return requestClient.post<any>(`${prefix}adjust-order-item-price`, data);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { Button } from 'ant-design-vue';
|
||||
import { ShoppingCartOutlined } from '@ant-design/icons-vue';
|
||||
import PatientOrderListModal from './PatientOrderListModal.vue';
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
const props = defineProps<{
|
||||
registerId?: number | null;
|
||||
}>();
|
||||
|
||||
const [OrderListDrawer, orderListDrawerApi] = useVbenDrawer({
|
||||
connectedComponent: PatientOrderListModal,
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
if (!props.registerId) {
|
||||
return;
|
||||
}
|
||||
orderListDrawerApi.setData({
|
||||
registerId: props.registerId,
|
||||
});
|
||||
orderListDrawerApi.open();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="registerId" class="order-list-float-button">
|
||||
<Button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
size="large"
|
||||
@click="handleClick"
|
||||
class="float-btn"
|
||||
style="width: 56px; height: 56px; padding: 0; display: flex; align-items: center; justify-content: center;"
|
||||
>
|
||||
<template #icon>
|
||||
<ShoppingCartOutlined :style="{ fontSize: '20px' }" />
|
||||
</template>
|
||||
</Button>
|
||||
<OrderListDrawer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.order-list-float-button {
|
||||
position: fixed;
|
||||
right: 24px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.float-btn {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.float-btn:hover {
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import { Button, message } from 'ant-design-vue';
|
||||
import { usePrescriptionStore } from '#/store/prescription';
|
||||
import { getProductListDoctorReception } from '#/views/doctor/doctor-reception/api';
|
||||
import { getDrugsByRegisterId } from '../api/index.ts';
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
@@ -40,58 +40,47 @@ const hasUsedDrugText = computed(() => {
|
||||
|
||||
// 添加药品到处方单
|
||||
const handleAddToPrescription = async () => {
|
||||
if (!experienceData.value.drug_name) {
|
||||
message.warning('该就诊经历中没有药品信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查处方store是否已初始化
|
||||
if (!prescriptionStore.currentRegisterId) {
|
||||
message.warning('请先接诊患者');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取门店ID
|
||||
let storeId = prescriptionStore.myStoreId;
|
||||
if (!storeId || storeId === 0) {
|
||||
if (prescriptionStore.myStoreList && prescriptionStore.myStoreList.length > 0) {
|
||||
storeId = prescriptionStore.myStoreList[0].id;
|
||||
} else {
|
||||
message.warning('请先选择诊所');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
addingDrug.value = true;
|
||||
try {
|
||||
// 通过药品名称搜索药品
|
||||
// 如果myStoreId为0,尝试使用myStoreList的第一个
|
||||
let storeId = prescriptionStore.myStoreId;
|
||||
if (!storeId || storeId === 0) {
|
||||
if (prescriptionStore.myStoreList && prescriptionStore.myStoreList.length > 0) {
|
||||
storeId = prescriptionStore.myStoreList[0].id;
|
||||
} else {
|
||||
message.warning('请先选择诊所');
|
||||
return;
|
||||
// 根据 register_id 获取药品信息
|
||||
const res = await getDrugsByRegisterId(prescriptionStore.currentRegisterId, storeId);
|
||||
const drugList = res?.result || res?.data || res || [];
|
||||
|
||||
if (!drugList || drugList.length === 0) {
|
||||
message.warning('未找到药品信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历药品列表,添加到处方单
|
||||
let addedCount = 0;
|
||||
for (const drugData of drugList) {
|
||||
const success = prescriptionStore.addProducts(drugData);
|
||||
if (success) {
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await getProductListDoctorReception({
|
||||
store_id: storeId,
|
||||
type: 2, // 中成(西)药
|
||||
name: experienceData.value.drug_name,
|
||||
});
|
||||
|
||||
if (!res || res.length === 0) {
|
||||
message.warning(`未找到药品:${experienceData.value.drug_name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用第一个匹配的药品
|
||||
const drugData = res[0];
|
||||
|
||||
// 检查药品是否已在处方中
|
||||
const existItem = prescriptionStore.currentDrugs.find(
|
||||
(item) => item.index_id === drugData.id,
|
||||
);
|
||||
if (existItem) {
|
||||
message.warning('该药品已在处方中');
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加药品到处方单
|
||||
const success = prescriptionStore.addProducts(drugData);
|
||||
if (success) {
|
||||
message.success(`已将${drugData.drug.drug_name}添加到处方单`);
|
||||
if (addedCount > 0) {
|
||||
message.success(`已成功添加 ${addedCount} 个药品到处方单`);
|
||||
} else {
|
||||
message.warning('所有药品已在处方中');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加药品失败:', error);
|
||||
@@ -315,7 +304,7 @@ const handleAddToPrescription = async () => {
|
||||
.drug-name {
|
||||
color: #d97706;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
|
||||
//background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
|
||||
padding: 4px 12px;
|
||||
border-radius: 6px;
|
||||
display: inline-block;
|
||||
|
||||
@@ -43,7 +43,7 @@ const fetchPatientDetail = async () => {
|
||||
registerId = id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 支持只有 register_id 没有 room_id 的情况
|
||||
const res = await getPatientDetail(props.patient.room_id || '', registerId);
|
||||
// 确保正确解析返回数据
|
||||
@@ -73,7 +73,7 @@ const handleReception = async () => {
|
||||
try {
|
||||
// 传递挂号ID
|
||||
const res = await reception(props.patient.register_id);
|
||||
|
||||
|
||||
// res 已经是 data.result 的内容,无需判断 code(请求封装会自动判断,失败会抛出异常)
|
||||
message.success('接诊成功');
|
||||
const receptionResult = {
|
||||
@@ -131,7 +131,8 @@ watch(
|
||||
<text>加载中...</text>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!patientDetail && !patient" class="empty-state">
|
||||
<div v-else-if="!patientDetail && !patient"
|
||||
class="empty-state flex items-center justify-center h-full w-full min-h-[300px]">
|
||||
<Empty description="请选择患者" />
|
||||
</div>
|
||||
|
||||
@@ -148,7 +149,7 @@ watch(
|
||||
进入聊天
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 患者基本信息 -->
|
||||
<Descriptions
|
||||
:column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }"
|
||||
@@ -227,7 +228,6 @@ watch(
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.patient-content {
|
||||
|
||||
@@ -65,6 +65,15 @@ const getStatusText = (status) => {
|
||||
return statusMap[status] || '未知';
|
||||
};
|
||||
|
||||
// 获取挂号类型文本
|
||||
const getRegisterTypeText = (registerType) => {
|
||||
const typeMap = {
|
||||
2: '诊所复诊',
|
||||
3: '药店复诊',
|
||||
};
|
||||
return typeMap[registerType] || '';
|
||||
};
|
||||
|
||||
// 获取状态样式类
|
||||
const getStatusClass = (status) => {
|
||||
const classMap = {
|
||||
@@ -164,9 +173,14 @@ defineExpose({
|
||||
<p class="patient-phone">{{ patient.user_patient?.mobile || patient.user?.mobile || '' }}</p>
|
||||
<p v-if="patient.order_no" class="order-no">订单号:{{ patient.order_no }}</p>
|
||||
<div class="patient-meta">
|
||||
<span :class="['status', getStatusClass(patient.status)]">
|
||||
{{ getStatusText(patient.status) }}
|
||||
</span>
|
||||
<div class="meta-left">
|
||||
<span :class="['status', getStatusClass(patient.status)]">
|
||||
{{ getStatusText(patient.status) }}
|
||||
</span>
|
||||
<span v-if="patient.register_type" class="register-type-tag">
|
||||
{{ getRegisterTypeText(patient.register_type) }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="time text-gray-500 dark:text-gray-400">{{ formatTime(patient.last_message_time) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -257,6 +271,29 @@ defineExpose({
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.meta-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.register-type-tag {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
border: 1px solid #91d5ff;
|
||||
}
|
||||
|
||||
.dark .register-type-tag {
|
||||
background: rgba(24, 144, 255, 0.2);
|
||||
color: #69c0ff;
|
||||
border-color: rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import { Tag, Button, message, Image } from 'ant-design-vue';
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getPatientOrders } from '../api/index.ts';
|
||||
import PriceAdjustModal from './PriceAdjustModal.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'PatientOrderListModal',
|
||||
});
|
||||
|
||||
interface Product {
|
||||
id: number;
|
||||
drug_id: number;
|
||||
drug_name: string;
|
||||
price: number;
|
||||
number: number;
|
||||
type: number;
|
||||
is_chinese_medicine: boolean;
|
||||
min_adjust_price: number | null;
|
||||
can_adjust: boolean;
|
||||
drug_image?: string;
|
||||
specification?: string;
|
||||
source_name?: string;
|
||||
}
|
||||
|
||||
interface Order {
|
||||
id: number;
|
||||
order_no: string;
|
||||
prescription_type: number;
|
||||
items_price: number;
|
||||
total_pay_price: number;
|
||||
trans_expenses?: number;
|
||||
status: number;
|
||||
is_pay: number;
|
||||
created_at: string;
|
||||
products: Product[];
|
||||
}
|
||||
|
||||
const [DrawerComponent, drawerApi] = useVbenDrawer({
|
||||
class: 'w-[1000px]',
|
||||
onCancel() {
|
||||
drawerApi.close();
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
const data = drawerApi.getData<{ registerId: number }>();
|
||||
if (data?.registerId) {
|
||||
registerId.value = data.registerId;
|
||||
fetchOrders();
|
||||
}
|
||||
} else {
|
||||
gridApi.setGridOptions({ data: [] });
|
||||
registerId.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [PriceAdjustDrawerComponent, priceAdjustDrawerApi] = useVbenDrawer({
|
||||
connectedComponent: PriceAdjustModal,
|
||||
});
|
||||
|
||||
const registerId = ref<number | null>(null);
|
||||
|
||||
const gridOptions: VxeGridProps<Order> = {
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
labelField: '',
|
||||
},
|
||||
columnConfig: {
|
||||
useKey: true,
|
||||
},
|
||||
rowConfig: {
|
||||
useKey: true,
|
||||
},
|
||||
columns: [
|
||||
{ type: 'expand', width: 80, slots: { content: 'expand-content' } },
|
||||
{ type: 'seq', width: 60, title: '序号' },
|
||||
{ field: 'order_no', title: '订单号', width: 180 },
|
||||
{
|
||||
field: 'prescription_type',
|
||||
title: '处方类型',
|
||||
width: 100,
|
||||
slots: { default: 'prescription_type' },
|
||||
},
|
||||
{
|
||||
field: 'items_price',
|
||||
title: '商品总价',
|
||||
width: 120,
|
||||
slots: { default: 'items_price' },
|
||||
},
|
||||
{
|
||||
field: 'total_pay_price',
|
||||
title: '支付总价',
|
||||
width: 120,
|
||||
slots: { default: 'total_pay_price' },
|
||||
},
|
||||
{
|
||||
field: 'trans_expenses',
|
||||
title: '邮费',
|
||||
width: 100,
|
||||
slots: { default: 'trans_expenses' },
|
||||
},
|
||||
{
|
||||
field: 'is_pay',
|
||||
title: '支付状态',
|
||||
width: 100,
|
||||
slots: { default: 'is_pay' },
|
||||
},
|
||||
{ field: 'created_at', title: '订单生成时间', width: 180 },
|
||||
{
|
||||
type: 'html',
|
||||
title: '操作',
|
||||
width: 120,
|
||||
slots: { default: 'action' },
|
||||
},
|
||||
],
|
||||
data: [],
|
||||
expandConfig: {},
|
||||
pagerConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
height: 'auto',
|
||||
border: false,
|
||||
showOverflow: false,
|
||||
};
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
gridOptions,
|
||||
showSearchForm: false,
|
||||
});
|
||||
|
||||
const getPrescriptionTypeText = (type: number): string => {
|
||||
const typeMap: Record<number, string> = {
|
||||
1: '中药',
|
||||
2: '西药',
|
||||
3: '颗粒药',
|
||||
};
|
||||
return typeMap[type] || '未知';
|
||||
};
|
||||
|
||||
const getPrescriptionTypeColor = (type: number): string => {
|
||||
const colorMap: Record<number, string> = {
|
||||
1: 'orange',
|
||||
2: 'blue',
|
||||
3: 'purple',
|
||||
};
|
||||
return colorMap[type] || 'default';
|
||||
};
|
||||
|
||||
const handleAdjustPrice = (product: Product, orderId: number) => {
|
||||
priceAdjustDrawerApi.setData({
|
||||
productOrderId: orderId,
|
||||
productOrderItemId: product.id,
|
||||
drugId: product.drug_id,
|
||||
drugName: product.drug_name,
|
||||
currentPrice: product.price,
|
||||
minAdjustPrice: product.min_adjust_price || 0,
|
||||
registerId: registerId.value,
|
||||
onSuccess: () => {
|
||||
fetchOrders();
|
||||
},
|
||||
});
|
||||
priceAdjustDrawerApi.open();
|
||||
};
|
||||
|
||||
const fetchOrders = async () => {
|
||||
if (!registerId.value) {
|
||||
return;
|
||||
}
|
||||
gridApi.setLoading(true);
|
||||
try {
|
||||
const res = await getPatientOrders(registerId.value);
|
||||
const orderList: Order[] = res?.result || res?.data || res || [];
|
||||
gridApi.setGridOptions({ data: orderList });
|
||||
} catch (error) {
|
||||
console.error('获取订单列表失败:', error);
|
||||
message.error('获取订单列表失败');
|
||||
} finally {
|
||||
gridApi.setLoading(false);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerComponent title="患者订单列表" class="order-list-drawer w-[60%]">
|
||||
<Grid>
|
||||
<!-- 处方类型 -->
|
||||
<template #prescription_type="{ row }">
|
||||
<Tag :color="getPrescriptionTypeColor(row.prescription_type)">
|
||||
{{ getPrescriptionTypeText(row.prescription_type) }}
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
<!-- 商品总价 -->
|
||||
<template #items_price="{ row }">
|
||||
¥{{ Number(row.items_price || 0).toFixed(2) }}
|
||||
</template>
|
||||
|
||||
<!-- 支付总价 -->
|
||||
<template #total_pay_price="{ row }">
|
||||
¥{{ Number(row.total_pay_price || 0).toFixed(2) }}
|
||||
</template>
|
||||
|
||||
<!-- 邮费 -->
|
||||
<template #trans_expenses="{ row }">
|
||||
¥{{ Number(row.trans_expenses || 0).toFixed(2) }}
|
||||
</template>
|
||||
|
||||
<!-- 支付状态 -->
|
||||
<template #is_pay="{ row }">
|
||||
<Tag :color="row.is_pay === 1 ? 'green' : 'red'">
|
||||
{{ row.is_pay === 1 ? '已支付' : '未支付' }}
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
<!-- 操作 -->
|
||||
<template #action="{ row }">
|
||||
<span style="color: #999">共 {{ row.products?.length || 0 }} 个商品</span>
|
||||
</template>
|
||||
|
||||
<!-- 展开内容:商品列表 -->
|
||||
<template #expand-content="{ row }">
|
||||
<div
|
||||
v-if="row.prescription_type === 2 || row.prescription_type === 5"
|
||||
class="product-list mt-5"
|
||||
>
|
||||
<div
|
||||
v-for="(product, index) in row.products"
|
||||
:key="product.id || index"
|
||||
class="box-card mb-6 rounded-lg p-4"
|
||||
>
|
||||
<div class="flex items-start">
|
||||
<div>
|
||||
<div
|
||||
class="mr-6 h-32 w-32 overflow-hidden rounded-lg bg-gray-200 dark:bg-gray-800"
|
||||
>
|
||||
<Image
|
||||
:src="product.drug_image || '/img/empty.png'"
|
||||
alt=""
|
||||
height="100%"
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-3 mt-5 text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">商品编号:</span>
|
||||
X00{{ index + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow space-y-2">
|
||||
<div class="text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">商品名称:</span>
|
||||
{{ product.drug_name }}
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">商品规格:</span>
|
||||
{{ product.specification || '暂无' }}
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">商品数量:</span>
|
||||
{{ product.number }}
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">商品厂家:</span>
|
||||
{{ product.source_name || '暂无' }}
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">商品价格:</span>
|
||||
<a
|
||||
v-if="!product.is_chinese_medicine && product.can_adjust"
|
||||
class="text-primary cursor-pointer hover:underline ml-1"
|
||||
@click="handleAdjustPrice(product, row.id)"
|
||||
>
|
||||
¥{{ Number(product.price || 0).toFixed(4) }}
|
||||
</a>
|
||||
<span v-else>¥{{ Number(product.price || 0).toFixed(4) }}</span>
|
||||
</div>
|
||||
<div v-if="!product.is_chinese_medicine && product.can_adjust" class="text-sm mt-2">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleAdjustPrice(product, row.id)"
|
||||
>
|
||||
调整价格
|
||||
</Button>
|
||||
<span class="text-gray-500 dark:text-gray-400 ml-2">
|
||||
(最低调整金额: ¥{{ Number(product.min_adjust_price || 0).toFixed(2) }})
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="product.is_chinese_medicine" class="text-sm">
|
||||
<span class="text-gray-500 dark:text-gray-400">备注:</span>
|
||||
<span style="color: #999">中药不可调整</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="row.prescription_type === 1"
|
||||
class="medication-details mt-5 p-10"
|
||||
>
|
||||
<span class="mb-2 font-medium">药品明细:</span>
|
||||
<ul class="custom-list pl-6">
|
||||
<li
|
||||
v-for="(product, index) in row.products"
|
||||
:key="product.id || index"
|
||||
class="custom-list-item"
|
||||
>
|
||||
【{{ product.drug_id }}】 {{ product.drug_name }} (* {{ product.number }})
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</Grid>
|
||||
<PriceAdjustDrawerComponent />
|
||||
</DrawerComponent>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.order-list-drawer {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.product-list {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
background-color: #fafafa;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.product-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.product-label {
|
||||
min-width: 120px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.product-value {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dark .product-item {
|
||||
background-color: #1f1f1f;
|
||||
border-color: #434343;
|
||||
}
|
||||
|
||||
.dark .product-label {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.dark .product-value {
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
.custom-list {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.custom-list-item {
|
||||
background-color: rgba(64, 158, 255, 0.04);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.custom-list-item:hover {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
.custom-list-item::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #409eff;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,167 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Form, InputNumber, message, Descriptions } from 'ant-design-vue';
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
import { adjustOrderItemPrice } from '../api/index.ts';
|
||||
|
||||
defineOptions({
|
||||
name: 'PriceAdjustModal',
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const loading = ref(false);
|
||||
|
||||
const formData = ref<{
|
||||
productOrderId?: number;
|
||||
productOrderItemId?: number;
|
||||
drugId?: number;
|
||||
drugName?: string;
|
||||
currentPrice?: number;
|
||||
minAdjustPrice?: number;
|
||||
registerId?: number | null;
|
||||
onSuccess?: () => void;
|
||||
}>({});
|
||||
|
||||
const newPrice = ref<number | null>(null);
|
||||
|
||||
// 价格验证规则
|
||||
const getPriceRules = () => {
|
||||
return [
|
||||
{ required: true, message: '请输入新价格' },
|
||||
{
|
||||
validator: (_rule: any, value: any) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!value && value !== 0) {
|
||||
reject(new Error('请输入新价格'));
|
||||
return;
|
||||
}
|
||||
const minPrice = formData.value.minAdjustPrice ? Number(formData.value.minAdjustPrice) : 0;
|
||||
if (minPrice > 0 && Number(value) < minPrice) {
|
||||
reject(new Error(`新价格不能低于最低调整金额:¥${minPrice.toFixed(2)}`));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const [DrawerComponent, drawerApi] = useVbenDrawer({
|
||||
class: 'w-[500px]',
|
||||
onCancel() {
|
||||
drawerApi.close();
|
||||
},
|
||||
onConfirm: async () => {
|
||||
// 验证表单
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
if (!formData.value.productOrderId || !formData.value.productOrderItemId || !newPrice.value) {
|
||||
message.error('参数不完整');
|
||||
return;
|
||||
}
|
||||
|
||||
const minPrice = formData.value.minAdjustPrice ? Number(formData.value.minAdjustPrice) : 0;
|
||||
if (minPrice > 0 && newPrice.value < minPrice) {
|
||||
message.error(`新价格不能低于最低调整金额:¥${minPrice.toFixed(2)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
await adjustOrderItemPrice({
|
||||
product_order_id: formData.value.productOrderId,
|
||||
product_order_item_id: formData.value.productOrderItemId,
|
||||
new_price: newPrice.value,
|
||||
register_id: formData.value.registerId || 0,
|
||||
});
|
||||
message.success('价格调整成功');
|
||||
|
||||
// 先执行回调刷新列表,再关闭抽屉
|
||||
const onSuccessCallback = formData.value.onSuccess;
|
||||
if (onSuccessCallback) {
|
||||
onSuccessCallback();
|
||||
}
|
||||
|
||||
// 延迟关闭,确保回调执行完成
|
||||
setTimeout(() => {
|
||||
drawerApi.close();
|
||||
}, 100);
|
||||
} catch (error: any) {
|
||||
console.error('调整价格失败:', error);
|
||||
message.error(error?.message || '调整价格失败,请重试');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
const data = drawerApi.getData<typeof formData.value>();
|
||||
if (data) {
|
||||
formData.value = {
|
||||
...data,
|
||||
currentPrice: data.currentPrice ? Number(data.currentPrice) : undefined,
|
||||
minAdjustPrice: data.minAdjustPrice ? Number(data.minAdjustPrice) : undefined,
|
||||
};
|
||||
newPrice.value = formData.value.currentPrice || null;
|
||||
}
|
||||
} else {
|
||||
formData.value = {};
|
||||
newPrice.value = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DrawerComponent
|
||||
title="调整商品价格"
|
||||
:loading="loading"
|
||||
>
|
||||
<div v-if="formData.drugName" class="price-adjust-modal">
|
||||
<Descriptions :column="1" bordered size="small" class="mb-4">
|
||||
<Descriptions.Item label="药品名称">
|
||||
{{ formData.drugName }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="当前价格">
|
||||
¥{{ formData.currentPrice ? Number(formData.currentPrice).toFixed(4) : '0.0000' }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="最低调整金额">
|
||||
<span v-if="formData.minAdjustPrice && Number(formData.minAdjustPrice) > 0">
|
||||
¥{{ Number(formData.minAdjustPrice).toFixed(2) }}
|
||||
</span>
|
||||
<span v-else style="color: #999">未设置</span>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Form ref="formRef" :model="{ newPrice }" layout="vertical">
|
||||
<Form.Item
|
||||
label="新价格"
|
||||
name="newPrice"
|
||||
:rules="getPriceRules()"
|
||||
>
|
||||
<InputNumber
|
||||
v-model:value="newPrice"
|
||||
:min="formData.minAdjustPrice ? Number(formData.minAdjustPrice) : 0"
|
||||
:precision="4"
|
||||
:step="0.01"
|
||||
style="width: 100%"
|
||||
placeholder="请输入新价格"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</DrawerComponent>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.price-adjust-modal {
|
||||
padding: 8px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -12,8 +12,8 @@ import { useUserStore } from '#/views/business/chat/stores/user';
|
||||
import { usePrescriptionStore } from '#/store/prescription';
|
||||
import PatientList from './components/PatientList.vue';
|
||||
import PatientInfoPanel from './components/PatientInfoPanel.vue';
|
||||
import { endConsultation } from './api/index.ts';
|
||||
import { getProductListDoctorReception } from '#/views/doctor/doctor-reception/api';
|
||||
import OrderListFloatButton from './components/OrderListFloatButton.vue';
|
||||
import { endConsultation, getDrugsByRegisterId } from './api/index.ts';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -280,16 +280,6 @@ const handleAddPatientProductsToPrescription = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从聊天记录中筛选出所有 message_type=11 的消息(患者就诊经历)
|
||||
const productMessages = chatStore.messages.filter(
|
||||
(msg) => msg.message_type === 11
|
||||
);
|
||||
|
||||
if (productMessages.length === 0) {
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取诊所ID
|
||||
let storeId = prescriptionStore.myStoreId;
|
||||
if (!storeId || storeId === 0) {
|
||||
@@ -301,69 +291,25 @@ const handleAddPatientProductsToPrescription = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
let addedCount = 0;
|
||||
let skippedCount = 0;
|
||||
const errors = [];
|
||||
|
||||
try {
|
||||
// 遍历每条商品消息,添加到处方单
|
||||
for (const msg of productMessages) {
|
||||
try {
|
||||
// 解析消息内容
|
||||
let productData;
|
||||
try {
|
||||
productData = typeof msg.message_content === 'string'
|
||||
? JSON.parse(msg.message_content)
|
||||
: msg.message_content;
|
||||
} catch (e) {
|
||||
console.error('解析就诊经历消息失败:', e);
|
||||
continue;
|
||||
}
|
||||
// 根据 register_id 获取药品信息
|
||||
const res = await getDrugsByRegisterId(prescriptionStore.currentRegisterId, storeId);
|
||||
const drugList = res?.result || res?.data || res || [];
|
||||
|
||||
// 获取药品名称(从就诊经历中提取 drug_name)
|
||||
const drugName = productData.drug_name;
|
||||
if (!drugName) {
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
if (!drugList || drugList.length === 0) {
|
||||
message.warning('未找到药品信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过药品名称搜索药品
|
||||
const res = await getProductListDoctorReception({
|
||||
store_id: storeId,
|
||||
type: 2, // 中成(西)药
|
||||
name: drugName,
|
||||
});
|
||||
|
||||
if (!res || res.length === 0) {
|
||||
errors.push(`未找到药品:${drugName}`);
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 使用第一个匹配的药品
|
||||
const drugData = res[0];
|
||||
|
||||
// 检查药品是否已在处方中
|
||||
const existItem = prescriptionStore.currentDrugs.find(
|
||||
(item) => item.index_id === drugData.id,
|
||||
);
|
||||
if (existItem) {
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 添加药品到处方单
|
||||
const success = prescriptionStore.addProducts(drugData);
|
||||
if (success) {
|
||||
addedCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加药品失败:', error);
|
||||
errors.push(`添加药品失败:${error.message || '未知错误'}`);
|
||||
// 遍历药品列表,添加到处方单
|
||||
let addedCount = 0;
|
||||
for (const drugData of drugList) {
|
||||
const success = prescriptionStore.addProducts(drugData);
|
||||
if (success) {
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示结果消息
|
||||
if (addedCount > 0) {
|
||||
message.success(`已成功添加 ${addedCount} 个药品到处方单`);
|
||||
// 打开处方模态框
|
||||
@@ -372,89 +318,11 @@ const handleAddPatientProductsToPrescription = async () => {
|
||||
onPrescriptionSent: handlePrescriptionSent,
|
||||
});
|
||||
prescriptionModalApi.open();
|
||||
} else if (skippedCount > 0) {
|
||||
// 如果所有药品都无法添加,尝试添加最新的那个
|
||||
// 按时间排序,取最新的药品(created_at 或 timestamp 最大的)
|
||||
const sortedMessages = [...productMessages].sort((a, b) => {
|
||||
const timeA = a.created_at ? new Date(a.created_at).getTime() : (a.timestamp || 0);
|
||||
const timeB = b.created_at ? new Date(b.created_at).getTime() : (b.timestamp || 0);
|
||||
return timeB - timeA; // 降序排列,最新的在前
|
||||
});
|
||||
|
||||
if (sortedMessages.length > 0) {
|
||||
const latestMessage = sortedMessages[0];
|
||||
try {
|
||||
// 解析最新消息的内容
|
||||
let productData;
|
||||
try {
|
||||
productData = typeof latestMessage.message_content === 'string'
|
||||
? JSON.parse(latestMessage.message_content)
|
||||
: latestMessage.message_content;
|
||||
} catch (e) {
|
||||
console.error('解析商品消息失败:', e);
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取药品名称
|
||||
const drugName = productData.drug_name || productData.name;
|
||||
if (!drugName) {
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
return;
|
||||
}
|
||||
|
||||
// 通过药品名称搜索药品
|
||||
const res = await getProductListDoctorReception({
|
||||
store_id: storeId,
|
||||
type: 2, // 中成(西)药
|
||||
name: drugName,
|
||||
});
|
||||
|
||||
if (!res || res.length === 0) {
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用第一个匹配的药品
|
||||
const drugData = res[0];
|
||||
|
||||
// 检查药品是否已在处方中
|
||||
const existItem = prescriptionStore.currentDrugs.find(
|
||||
(item) => item.index_id === drugData.id,
|
||||
);
|
||||
if (existItem) {
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加药品到处方单
|
||||
const success = prescriptionStore.addProducts(drugData);
|
||||
if (success) {
|
||||
message.success(`已成功添加 ${drugData.drug.drug_name} 到处方单`);
|
||||
// 打开处方模态框
|
||||
prescriptionModalApi.setData({
|
||||
registerId: prescriptionStore.currentRegisterId,
|
||||
onPrescriptionSent: handlePrescriptionSent,
|
||||
});
|
||||
prescriptionModalApi.open();
|
||||
} else {
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加最新药品失败:', error);
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
}
|
||||
} else {
|
||||
message.warning('当前聊天没有可添加的药品');
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有错误,显示错误信息
|
||||
if (errors.length > 0) {
|
||||
console.warn('添加药品时的错误:', errors);
|
||||
} else {
|
||||
message.warning('所有药品已在处方中');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量添加药品失败:', error);
|
||||
console.error('添加药品失败:', error);
|
||||
message.error('添加药品失败,请重试');
|
||||
}
|
||||
};
|
||||
@@ -476,6 +344,9 @@ const handleAddPatientProductsToPrescription = async () => {
|
||||
<!-- 处方模态框 - 使用 VbenModal 模式 -->
|
||||
<PrescriptionModalComponent />
|
||||
|
||||
<!-- 订单列表浮动按钮 -->
|
||||
<OrderListFloatButton :register-id="registerId" />
|
||||
|
||||
<!-- 左侧患者列表 -->
|
||||
<div class="left-panel-wrapper">
|
||||
<Button
|
||||
|
||||
@@ -15,6 +15,7 @@ import { TableAction } from '#/components/table-action';
|
||||
|
||||
// 导入药店管理相关的API(直接使用store的API)
|
||||
import {
|
||||
batchSyncDrugPriceApi,
|
||||
deleteStore,
|
||||
openPcWindowsApiByStore,
|
||||
openQrCodeApi,
|
||||
@@ -175,6 +176,26 @@ const updateSubscribe = (id: number) => {
|
||||
gridApi.query();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量同步总仓库药品
|
||||
*/
|
||||
const batchSyncDrugPrice = () => {
|
||||
const records = gridApi.grid.getCheckboxRecords();
|
||||
if (records.length === 0) {
|
||||
message.warning('请至少选择一条记录!');
|
||||
return;
|
||||
}
|
||||
const ids = records.map((item) => item.id);
|
||||
batchSyncDrugPriceApi(ids)
|
||||
.then((res) => {
|
||||
message.success(`批量同步任务已创建,共 ${res.data.success} 个门店的任务已提交!`);
|
||||
gridApi.query();
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('批量同步失败!');
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -203,6 +224,15 @@ const updateSubscribe = (id: number) => {
|
||||
confirm: deleteApi.bind(null, false),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '同步总仓库药品',
|
||||
icon: 'ant-design:sync-outlined',
|
||||
ifShow: hasTopTableDropDownActions,
|
||||
popConfirm: {
|
||||
title: '确定要同步选中门店的总仓库药品吗?同步将更新价格、上架下架状态,并自动添加新药品。',
|
||||
confirm: batchSyncDrugPrice,
|
||||
},
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #more>
|
||||
|
||||
@@ -139,3 +139,13 @@ export async function updateStoreDrugPricesApi(data: {
|
||||
}) {
|
||||
return requestClient.post<any>(`${prefix}update-store-drug-prices`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量同步总仓库药品
|
||||
* @param ids 门店ID数组
|
||||
*/
|
||||
export async function batchSyncDrugPriceApi(ids: number[]) {
|
||||
return requestClient.post<any>(`${prefix}batch-sync-drug-price`, {
|
||||
ids,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import QrCodePreview from '#/components/modal/QrCodePreview.vue';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
|
||||
import {
|
||||
batchSyncDrugPriceApi,
|
||||
deleteStore,
|
||||
openPcWindowsApiByStore,
|
||||
openQrCodeApi,
|
||||
@@ -168,6 +169,26 @@ const updateSubscribe = (id: number) => {
|
||||
gridApi.query();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量同步总仓库药品
|
||||
*/
|
||||
const batchSyncDrugPrice = () => {
|
||||
const records = gridApi.grid.getCheckboxRecords();
|
||||
if (records.length === 0) {
|
||||
message.warning('请至少选择一条记录!');
|
||||
return;
|
||||
}
|
||||
const ids = records.map((item) => item.id);
|
||||
batchSyncDrugPriceApi(ids)
|
||||
.then((res) => {
|
||||
message.success(`批量同步任务已创建,共 ${res.data.success} 个门店的任务已提交!`);
|
||||
gridApi.query();
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('批量同步失败!');
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -199,6 +220,15 @@ const updateSubscribe = (id: number) => {
|
||||
confirm: deleteApi.bind(null, false),
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '同步总仓库药品',
|
||||
icon: 'ant-design:sync-outlined',
|
||||
ifShow: hasTopTableDropDownActions,
|
||||
popConfirm: {
|
||||
title: '确定要同步选中门店的总仓库药品吗?同步将更新价格、上架下架状态,并自动添加新药品。',
|
||||
confirm: batchSyncDrugPrice,
|
||||
},
|
||||
},
|
||||
]"
|
||||
>
|
||||
<template #more>
|
||||
|
||||
Reference in New Issue
Block a user