feat:优化了门店搜索组件、优化了模态框默认的内容

This commit is contained in:
李琦
2026-07-23 14:23:32 +08:00
parent 700db76f16
commit bbb133476a
54 changed files with 622 additions and 82 deletions

View File

@@ -11,6 +11,7 @@ import {
addWestPrescription,
checkChineseMedicineConflictApi,
getCurrentStoreTypeApi,
getPrescriptionTypeOptionsApi,
getDrugUseList,
getMyStoreListApi,
getPatientItem,
@@ -54,6 +55,16 @@ export const usePrescriptionStore = defineStore('prescription', () => {
// 处方状态
const myStoreId = ref(0);
const activeCategory = ref(2);
/** 后端下发的处方类型列表(含可选 icon / icon_text */
const categories = ref([
{ label: '中药', value: 1, icon: '', icon_text: '' },
{ label: '西(中成)药', value: 2, icon: '', icon_text: '' },
{ label: '保健食品', value: 3, icon: '', icon_text: '' },
{ label: '产品服务包', value: 5, icon: '', icon_text: '' },
{ label: '非药品', value: 6, icon: '', icon_text: '' },
{ label: '医疗器械', value: 7, icon: '', icon_text: '' },
]);
const prescriptionTypeDefault = ref(2);
const diagnosis = ref('');
const medicalAdvice = ref('');
const treatmentPrice = ref(0);
@@ -192,6 +203,33 @@ export const usePrescriptionStore = defineStore('prescription', () => {
return calcItemMarginPercent(drug.price, drug.buy_price);
};
/**
* 拉取后端处方类型列表与默认选中
*/
const loadPrescriptionTypeOptions = async (registerId?: number | string) => {
try {
const params: { register_id?: number; store_id?: number } = {};
const rid = Number(registerId || currentRegisterId.value);
if (rid) params.register_id = rid;
if (myStoreId.value) params.store_id = myStoreId.value;
const res = await getPrescriptionTypeOptionsApi(params);
const list = Array.isArray(res?.list) ? res.list : [];
if (list.length) {
categories.value = list.map((item) => ({
value: Number(item.value),
label: item.label || '',
icon: item.icon || '',
icon_text: item.icon_text || '',
}));
}
const def = Number(res?.default);
if (def) prescriptionTypeDefault.value = def;
} catch (error) {
console.error('加载处方类型失败:', error);
message.warning('处方类型加载失败,请稍后重试');
}
};
// localStorage同步方法
const syncToLocalStorage = () => {
try {
@@ -260,6 +298,12 @@ export const usePrescriptionStore = defineStore('prescription', () => {
// 设置当前注册ID
currentRegisterId.value = registerId;
// 先拉类型选项,再决定默认 Tab无本地记忆时用后端 default
if (!isInitialized.value) {
await initializeBasicData();
}
await loadPrescriptionTypeOptions(registerId);
// 恢复之前保存的 activeCategory
const savedCategory = localStorage.getItem(
`${storagePrefix.value}activeCategory${registerId}`
@@ -278,17 +322,14 @@ export const usePrescriptionStore = defineStore('prescription', () => {
activeCategory.value = 1;
} else if (westData && JSON.parse(westData).length > 0) {
activeCategory.value = 2;
} else if (prescriptionTypeDefault.value) {
activeCategory.value = prescriptionTypeDefault.value;
}
}
// 加载localStorage数据
loadFromLocalStorage();
// 如果基础数据未初始化,先初始化基础数据
if (!isInitialized.value) {
await initializeBasicData();
}
await fetchStoreSeeRate();
// 获取患者信息(只在需要时调用,如 PrescriptionModal
@@ -1113,6 +1154,11 @@ export const usePrescriptionStore = defineStore('prescription', () => {
} else {
currentDrugs.value.splice(0, currentDrugs.value.length);
}
// 切换后聚焦 Tab 栏当前项
nextTick(() => {
const el = document.getElementById(`rx-modal-tab-${categoryValue}`);
el?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
});
};
// 工具函数
@@ -1152,6 +1198,8 @@ export const usePrescriptionStore = defineStore('prescription', () => {
myStoreList,
myStoreId,
activeCategory,
categories,
prescriptionTypeDefault,
diagnosis,
medicalAdvice,
treatmentPrice,
@@ -1188,6 +1236,7 @@ export const usePrescriptionStore = defineStore('prescription', () => {
initializePrescription,
initializeForModal,
initializeBasicData,
loadPrescriptionTypeOptions,
resetInitializationState,
getDrugList,
addProducts,

View File

@@ -77,18 +77,15 @@ const commonPrescriptionName = ref('');
/** 是否正在保存常用方 */
const isSavingCommonPrescription = ref(false);
const categories = [
{ label: '中药', value: 1 },
{ label: '中成(西)药', value: 2 },
{ label: '保健食品', value: 3 },
{ label: '产品服务包', value: 5 },
{ label: '非药品', value: 6 },
{ label: '医疗器械', value: 7 },
];
// 使用 Pinia store
const prescriptionStore = usePrescriptionStore();
/** 处方类型来自后端下发store.categories */
const categories = computed(() => prescriptionStore.categories);
const rxSwipeStartX = ref(0);
const rxSwipeStartY = ref(0);
const allowInsuranceCategory = computed(
() => Number(prescriptionStore.registerStoreInfo?.allow_insurance_category ?? 0) === 1,
);
@@ -133,6 +130,8 @@ const [Modal, modalApi] = useVbenModal({
storagePrefix,
);
prescriptionStore.loadFromLocalStorage();
// 开方弹窗打开时再拉一次类型,确保 Network 可见且数据最新
await prescriptionStore.loadPrescriptionTypeOptions(modalData.value.registerId);
// 获取挂号诊所信息
await prescriptionStore.fetchRegisterStoreInfo();
@@ -446,6 +445,26 @@ const tabChange = async (id: number) => {
}
};
/** 记录左右滑起点 */
const onRxPanelPointerDown = (e: PointerEvent) => {
rxSwipeStartX.value = e.clientX;
rxSwipeStartY.value = e.clientY;
};
/** 左右滑切换相邻处方类型 */
const onRxPanelPointerUp = (e: PointerEvent) => {
const dx = e.clientX - rxSwipeStartX.value;
const dy = e.clientY - rxSwipeStartY.value;
if (Math.abs(dx) < 80 || Math.abs(dx) <= Math.abs(dy)) return;
const list = categories.value || [];
if (list.length < 2) return;
const idx = list.findIndex((c) => Number(c.value) === Number(prescriptionStore.activeCategory));
if (idx < 0) return;
const nextIdx = dx < 0 ? idx + 1 : idx - 1;
if (nextIdx < 0 || nextIdx >= list.length) return;
tabChange(list[nextIdx].value);
};
/**
* 检测并显示转诊提示
* @description 当切换到中药处方时,如果当前诊所为西医诊所,显示转诊提示
@@ -1021,16 +1040,30 @@ const cancelSaveCommonPrescription = () => {
<div class="drug-categories sticky top-0 bg-white dark:bg-[#151515] z-10 py-2 border-b mb-4">
<button
v-for="categoryItem in categories"
:id="`rx-modal-tab-${categoryItem.value}`"
:key="categoryItem.value"
:class="{
active: prescriptionStore.activeCategory === categoryItem.value,
}"
@click="tabChange(categoryItem.value)"
>
{{ categoryItem.label }}
<img
v-if="categoryItem.icon"
class="tab-type-icon"
:src="categoryItem.icon"
alt=""
/>
<span>{{ categoryItem.label }}</span>
<span v-if="categoryItem.icon_text" class="tab-type-badge">{{ categoryItem.icon_text }}</span>
</button>
</div>
<div
class="rx-swipe-panel"
@pointerdown="onRxPanelPointerDown"
@pointerup="onRxPanelPointerUp"
>
<!-- 费用类型选择和添加商品按钮 -->
<div class="mb-4 flex items-center justify-between">
<RadioGroup v-if="allowInsuranceCategory" v-model:value="prescriptionStore.category">
@@ -1067,6 +1100,13 @@ const cancelSaveCommonPrescription = () => {
</div>
</div>
<!-- 中药:顶部固定展示已选种数 -->
<div
v-if="prescriptionStore.activeCategory === 1"
class="mb-3 mt-2 text-base font-medium text-gray-700"
>
已选 {{ prescriptionStore.currentDrugs.length }} 种药
</div>
<!-- 中药药品列表 -->
<div
v-if="prescriptionStore.activeCategory === 1"
@@ -1575,6 +1615,7 @@ const cancelSaveCommonPrescription = () => {
</div>
</div>
</div>
</div>
</div>
<!-- 以下是各种弹窗组件,位置不动 -->
@@ -1697,6 +1738,8 @@ const cancelSaveCommonPrescription = () => {
display: flex;
gap: 1rem;
/* margin: 1rem 0; 已在类中通过 sticky 处理 */
overflow-x: auto;
flex-wrap: nowrap;
}
.drug-categories button {
@@ -1707,6 +1750,11 @@ const cancelSaveCommonPrescription = () => {
background: transparent;
color: inherit;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
white-space: nowrap;
}
.dark .drug-categories button {
@@ -1720,6 +1768,25 @@ const cancelSaveCommonPrescription = () => {
background: #455cda;
}
.tab-type-icon {
width: 16px;
height: 16px;
object-fit: contain;
}
.tab-type-badge {
font-size: 11px;
color: #e6a23c;
background: #fdf6ec;
padding: 0 6px;
border-radius: 4px;
line-height: 1.4;
}
.rx-swipe-panel {
touch-action: pan-y;
}
.selected-drugs {
margin: 1rem 0;
border: 1px solid #eee;

View File

@@ -57,7 +57,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}快递公司`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}快递公司`" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<Form />
</Modal>
</template>

View File

@@ -37,7 +37,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="处方溯源">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="处方溯源">
<PrescriptionSourceContent :data="data" />
</Modal>
</template>

View File

@@ -79,6 +79,18 @@ export async function expressDetailByOrderId(data: Record<string, any>) {
return requestClient.post<any>(`express-detail/detail-by-order`, data);
}
/**
* 修改已发货运单:快递单号 / 查件手机号 / 快递公司
*/
export async function updateExpressNosApi(data: {
express_no_id: number;
express_no?: string;
mobile?: string;
express_company_code?: string;
}) {
return requestClient.post<any>(`express-detail/update-express-nos`, data);
}
/**
* 超管:将历史订单级运单同步到分包裹 shipment
*/

View File

@@ -188,6 +188,20 @@ function packageTabLabel(pkg: Record<string, any>, idx: number): string {
return name ? `包裹${no}${name}` : `包裹${no}`;
}
/**
* 收件人姓名脱敏:保留首字,其余用 *(与物流动态弹窗一致)
*/
function maskExpressName(name: unknown): string {
const str = String(name || '').trim();
if (!str) {
return '-';
}
if (str.length === 1) {
return `${str}*`;
}
return str.slice(0, 1) + '*'.repeat(str.length - 1);
}
/**
* 打开独立物流动态弹窗(完整轨迹)
*/
@@ -249,7 +263,7 @@ function prescriptionStatusColor() {
}
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="订单详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="订单详情">
<div v-if="data" class="flex flex-col gap-4">
<Space>
<Button type="primary" size="small" @click="openOrderTrace">
@@ -528,6 +542,16 @@ function prescriptionStatusColor() {
:column="{ xxl: 2, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }"
bordered
>
<Descriptions.Item label="收件人">
{{
maskExpressName(
(expressDetail as any).express_name || data?.express_name,
)
}}
</Descriptions.Item>
<Descriptions.Item label="手机号">
<SensitiveText :record="pkg.express" field="mobile" />
</Descriptions.Item>
<Descriptions.Item label="物流公司">
{{ pkg.express.express_company_name || '-' }}
</Descriptions.Item>
@@ -553,6 +577,16 @@ function prescriptionStatusColor() {
bordered
class="mt-4"
>
<Descriptions.Item label="收件人">
{{
maskExpressName(
(expressDetail as any).express_name || data?.express_name,
)
}}
</Descriptions.Item>
<Descriptions.Item label="手机号">
<SensitiveText :record="expressDetail" field="mobile" />
</Descriptions.Item>
<Descriptions.Item label="物流公司">
{{ expressDetail.express_company_name || '-' }}
</Descriptions.Item>

View File

@@ -2,14 +2,30 @@
/**
* 独立「物流动态」弹窗:只请求 express-detail不展示整单其它信息。
* 仓名为空时 Tab 仅显示「包裹N」与后端诊所开关抹名约定一致。
* 已发货包裹展示脱敏收件人/查件手机号,并支持修改手机号与快递单号。
*/
import { computed, ref } from 'vue';
import { computed, reactive, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Descriptions, Spin, Tabs, Tag, Timeline } from 'ant-design-vue';
import {
Button,
Descriptions,
Form,
FormItem,
Input,
Modal as AModal,
Spin,
Tabs,
Tag,
Timeline,
message,
} from 'ant-design-vue';
import { expressDetailByOrderId } from '../api';
import ExpressCompanySelect from '#/components/form/components/express-company-select.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { expressDetailByOrderId, updateExpressNosApi } from '../api';
defineOptions({
name: 'ProductOrderLogisticsModal',
@@ -20,6 +36,16 @@ const expressDetail = ref<Record<string, any>>({});
const activePkg = ref('0');
const orderId = ref<number | null>(null);
/** 改运单弹窗 */
const editVisible = ref(false);
const editSubmitting = ref(false);
const editForm = reactive({
express_no_id: 0,
express_no: '',
mobile: '',
express_company_code: '' as string | undefined,
});
/**
* 多包裹列表:以物流接口 packages 为准(门店角色已按开关抹仓名)
*/
@@ -37,6 +63,32 @@ function packageTabLabel(pkg: Record<string, any>, idx: number): string {
return name ? `包裹${no}${name}` : `包裹${no}`;
}
/**
* 收件人姓名脱敏:保留首字,其余用 *
*/
function maskExpressName(name: unknown): string {
const str = String(name || '').trim();
if (!str) {
return '-';
}
if (str.length === 1) {
return `${str}*`;
}
return str.slice(0, 1) + '*'.repeat(str.length - 1);
}
/**
* 解析运单 ID优先 express_no_id其次 express.id
*/
function resolveExpressNoId(
express: Record<string, any> | null | undefined,
fallbackId?: number,
): number {
const fromExpress = Number(express?.id || 0);
const fromFallback = Number(fallbackId || 0);
return fromExpress > 0 ? fromExpress : fromFallback;
}
/**
* 物流状态 Tag 颜色
*/
@@ -60,16 +112,73 @@ function getColor(status: any) {
}
}
async function loadExpress(id: number) {
async function loadExpress(id: number, keepActive = false) {
loading.value = true;
try {
expressDetail.value = (await expressDetailByOrderId({ order_id: id })) || {};
activePkg.value = '0';
if (!keepActive) {
activePkg.value = '0';
}
} finally {
loading.value = false;
}
}
/**
* 打开改运单表单(包裹或旧单运单共用)
*/
function openEditExpress(payload: {
express_no_id: number;
express_no?: string;
mobile?: string;
express_company_code?: string;
}) {
const id = Number(payload.express_no_id || 0);
if (id <= 0) {
message.warning('缺少运单信息,无法修改');
return;
}
editForm.express_no_id = id;
editForm.express_no = String(payload.express_no || '');
editForm.mobile = String(payload.mobile || '');
editForm.express_company_code = payload.express_company_code || undefined;
editVisible.value = true;
}
/**
* 提交修改运单号 / 查件手机号 / 快递公司
*/
async function submitEditExpress() {
if (!editForm.express_no?.trim()) {
message.warning('请输入快递单号');
return;
}
if (!editForm.express_company_code) {
message.warning('请选择快递公司');
return;
}
if (!editForm.mobile?.trim()) {
message.warning('请输入手机号');
return;
}
editSubmitting.value = true;
try {
await updateExpressNosApi({
express_no_id: editForm.express_no_id,
express_no: editForm.express_no.trim(),
mobile: editForm.mobile.trim(),
express_company_code: editForm.express_company_code,
});
message.success('修改成功');
editVisible.value = false;
if (orderId.value) {
await loadExpress(orderId.value, true);
}
} finally {
editSubmitting.value = false;
}
}
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
@@ -84,6 +193,7 @@ const [Modal, modalApi] = useVbenModal({
const id = Number(payload.order_id || payload.id || 0);
orderId.value = id || null;
expressDetail.value = {};
editVisible.value = false;
if (id) {
loadExpress(id);
}
@@ -91,6 +201,7 @@ const [Modal, modalApi] = useVbenModal({
orderId.value = null;
expressDetail.value = {};
activePkg.value = '0';
editVisible.value = false;
}
},
});
@@ -118,11 +229,57 @@ const [Modal, modalApi] = useVbenModal({
:column="{ xxl: 2, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }"
bordered
>
<Descriptions.Item label="收件人">
{{ maskExpressName(expressDetail.express_name) }}
</Descriptions.Item>
<Descriptions.Item label="手机号">
<span class="inline-flex items-center gap-2">
<SensitiveText :record="pkg.express" field="mobile" />
<Button
type="link"
size="small"
class="!px-0"
@click="
openEditExpress({
express_no_id: resolveExpressNoId(
pkg.express,
pkg.express_no_id,
),
express_no: pkg.express.express_no,
mobile: pkg.express.mobile,
express_company_code: pkg.express.express_company_code,
})
"
>
修改
</Button>
</span>
</Descriptions.Item>
<Descriptions.Item label="物流公司">
{{ pkg.express.express_company_name || '-' }}
</Descriptions.Item>
<Descriptions.Item label="运单号">
{{ pkg.express.express_no || '-' }}
<span class="inline-flex items-center gap-2">
<span>{{ pkg.express.express_no || '-' }}</span>
<Button
type="link"
size="small"
class="!px-0"
@click="
openEditExpress({
express_no_id: resolveExpressNoId(
pkg.express,
pkg.express_no_id,
),
express_no: pkg.express.express_no,
mobile: pkg.express.mobile,
express_company_code: pkg.express.express_company_code,
})
"
>
修改
</Button>
</span>
</Descriptions.Item>
<Descriptions.Item label="最新状态">
{{ pkg.express.state_txt || '-' }}
@@ -153,11 +310,59 @@ const [Modal, modalApi] = useVbenModal({
:column="{ xxl: 2, xl: 2, lg: 2, md: 1, sm: 1, xs: 1 }"
bordered
>
<Descriptions.Item label="收件人">
{{ maskExpressName(expressDetail.express_name) }}
</Descriptions.Item>
<Descriptions.Item label="手机号">
<span class="inline-flex items-center gap-2">
<SensitiveText :record="expressDetail" field="mobile" />
<Button
v-if="resolveExpressNoId(expressDetail, expressDetail.express_no_id)"
type="link"
size="small"
class="!px-0"
@click="
openEditExpress({
express_no_id: resolveExpressNoId(
expressDetail,
expressDetail.express_no_id,
),
express_no: expressDetail.express_no,
mobile: expressDetail.mobile,
express_company_code: expressDetail.express_company_code,
})
"
>
修改
</Button>
</span>
</Descriptions.Item>
<Descriptions.Item label="物流公司">
{{ expressDetail.express_company_name || '-' }}
</Descriptions.Item>
<Descriptions.Item label="运单号">
{{ expressDetail.express_no || '-' }}
<span class="inline-flex items-center gap-2">
<span>{{ expressDetail.express_no || '-' }}</span>
<Button
v-if="resolveExpressNoId(expressDetail, expressDetail.express_no_id)"
type="link"
size="small"
class="!px-0"
@click="
openEditExpress({
express_no_id: resolveExpressNoId(
expressDetail,
expressDetail.express_no_id,
),
express_no: expressDetail.express_no,
mobile: expressDetail.mobile,
express_company_code: expressDetail.express_company_code,
})
"
>
修改
</Button>
</span>
</Descriptions.Item>
<Descriptions.Item label="最新状态">
{{ expressDetail.state_txt || '-' }}
@@ -183,6 +388,39 @@ const [Modal, modalApi] = useVbenModal({
</template>
</Spin>
</Modal>
<!-- 修改运单号 / 查件手机号与发货表单字段对齐改单号时可重匹配公司 -->
<AModal
v-model:open="editVisible"
title="修改运单信息"
:confirm-loading="editSubmitting"
destroy-on-close
@ok="submitEditExpress"
>
<Form layout="vertical" class="mt-2">
<FormItem label="快递单号" required>
<Input
v-model:value="editForm.express_no"
placeholder="请输入快递单号"
allow-clear
/>
</FormItem>
<FormItem label="快递公司" required>
<ExpressCompanySelect
v-model:value="editForm.express_company_code"
placeholder="请选择快递公司"
:tracking-no="editForm.express_no"
/>
</FormItem>
<FormItem label="手机号" required>
<Input
v-model:value="editForm.mobile"
placeholder="请输入查件手机号"
allow-clear
/>
</FormItem>
</Form>
</AModal>
</template>
<style scoped>

View File

@@ -65,7 +65,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`订单:【 ${orderNo} 】发货`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal :title="`订单:【 ${orderNo} 】发货`" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<Form />
</Modal>
</template>

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
title="退款"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -901,9 +901,9 @@ const openOrderAmountVerify = () => {
<div class="text-xs text-gray-500">
<Button
v-if="row.store?.id"
type="link"
class="!h-auto max-w-[xxx] whitespace-normal break-words !px-0 text-left"
size="small"
class="!h-auto !px-0"
type="link"
@click="openStoreCard(row.store.id)"
>
{{ row.store?.name || '—' }}

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
title="退款"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -80,7 +80,7 @@ const handleChange = (info: { file: UploadFile }) => {
};
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="上传Excel">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="上传Excel">
<!-- 新增显示文件选择状态 -->
<div v-if="isFileSelected" class="mb-4 p-3 bg-green-50 border border-green-200 rounded">
<p class="text-green-700 text-sm">已选择文件{{ selectedFile?.name }}</p>

View File

@@ -123,7 +123,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}中药`"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -102,7 +102,7 @@ const handleChange = (info: { file: UploadFile }) => {
};
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="上传Excel">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="上传Excel">
<!-- 显示文件选择状态提示 -->
<div
v-if="isFileSelected"

View File

@@ -142,7 +142,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}保健食品`"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -73,7 +73,7 @@ const handleChange = (info: { file: UploadFile }) => {
};
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="上传Excel">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="上传Excel">
<div
v-if="isFileSelected"
class="mb-4 rounded border border-green-200 bg-green-50 p-3"

View File

@@ -96,7 +96,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}医疗器械`"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -73,7 +73,7 @@ const handleChange = (info: { file: UploadFile }) => {
};
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="上传Excel">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="上传Excel">
<div
v-if="isFileSelected"
class="mb-4 rounded border border-green-200 bg-green-50 p-3"

View File

@@ -96,7 +96,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}非药品`"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -66,7 +66,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}产品服务包`"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -80,7 +80,7 @@ const handleChange = (info: { file: UploadFile }) => {
};
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="上传Excel">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="上传Excel">
<!-- 新增显示文件选择状态 -->
<div
v-if="isFileSelected"

View File

@@ -152,7 +152,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}西(中成)药`"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -100,7 +100,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal title="编辑药方" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal title="编辑药方" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<div v-if="prescriptionType === 'chinese'">
<ChineseDrugEditor
ref="chineseDrugEditorRef"

View File

@@ -178,7 +178,7 @@ function bindPrescriptionTypeChange() {
</script>
<template>
<Modal :title="`${isUpdate ? '编辑' : '新增'}特色方`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal :title="`${isUpdate ? '编辑' : '新增'}特色方`" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<Form />
<div v-if="prescriptionType === 'chinese'" class="mt-4 border-t pt-4">
<ChineseDrugEditor

View File

@@ -58,7 +58,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}轮播图`"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -132,7 +132,7 @@ const exportWarehouseDrugManagementTemplate = () => {
};
</script>
<template>
<Modal :title="importType === 1 ? '上传Excel' : titles" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal :title="importType === 1 ? '上传Excel' : titles" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<Button
v-if="importType === 1"
type="link"

View File

@@ -112,7 +112,7 @@ const exportWarehouseDrugManagementStoreTemplate = () => {
<template>
<Modal
:title="importType === 1 ? '上传Excel' : '批量修改价格'"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Button
v-if="importType === 1"

View File

@@ -126,7 +126,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}仓库药品`"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -115,7 +115,7 @@ const exportWarehouseDrugManagementStoreTemplate = () => {
<template>
<Modal
:title="importType === 1 ? '上传Excel' : '批量修改价格'"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Button
v-if="importType === 1"

View File

@@ -126,7 +126,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}仓库药品`"
class="w-[80%] md:w-[50%] lg:w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]"
>
<Form />
</Modal>

View File

@@ -104,6 +104,26 @@ export async function getCurrentStoreTypeApi(params?: {
});
}
/**
* 开方处方类型选项(含默认选中、可选 icon / icon_text
*/
export async function getPrescriptionTypeOptionsApi(params?: {
register_id?: number;
store_id?: number;
}) {
return requestClient.get<{
default: number;
list: Array<{
value: number;
label: string;
icon?: string;
icon_text?: string;
}>;
}>(`${prefix}prescription-type-options`, {
params,
});
}
/**
* 接诊
*/

View File

@@ -46,6 +46,13 @@ const userStore = useUserStore();
const searchKey = ref('');
// 药品类型1-中药2-西药
const type = ref(1);
/** 弹窗标题:中药时展示已选种数 */
const modalTitle = computed(() => {
if (Number(type.value) === 1) {
return `选择中药(${selectList.value.length}种)`;
}
return '商品列表';
});
// 当前药品回调函数
const currentDrugsWestern = ref();
// 当前患者ID
@@ -585,7 +592,7 @@ function updateProductNumber(id, number) {
</script>
<template>
<Modal class="w-[60%]" title="商品列表">
<Modal class="w-[60%]" :title="modalTitle">
<!-- 图片预览组件 -->
<Image
:preview="{

View File

@@ -39,6 +39,7 @@ import {
getDrugUseList,
getMyStoreListApi,
getCurrentStoreTypeApi,
getPrescriptionTypeOptionsApi,
getPatientItem,
getPatientList,
getPrescriptionInfoApi,
@@ -173,8 +174,39 @@ function getMyStoreList() {
getMyStoreList();
/** 拉取处方类型列表与默认选中 */
async function loadPrescriptionTypeOptions(registerId?: number) {
try {
const params: { register_id?: number; store_id?: number } = {};
if (registerId) params.register_id = registerId;
if (myStoreId.value) params.store_id = myStoreId.value;
const res = await getPrescriptionTypeOptionsApi(params);
const list = Array.isArray(res?.list) ? res.list : [];
if (list.length) {
categories.value = list.map((item) => ({
value: Number(item.value),
label: item.label || '',
icon: item.icon || '',
icon_text: item.icon_text || '',
}));
}
const def = Number(res?.default);
if (def) prescriptionTypeDefault.value = def;
} catch (e) {
console.error('加载处方类型失败', e);
message.warning('处方类型加载失败,请稍后重试');
}
}
/** 将当前分类 Tab 滚入可视区 */
function focusActiveCategoryTab() {
const el = document.getElementById(`rx-pc-tab-${activeCategory.value}`);
el?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
}
const userStore = useUserStore();
const myStoreId = ref(userStore.userInfo.store_id);
loadPrescriptionTypeOptions();
/** 当前取价门店是否允许查看毛利率0/1 */
const seeRate = ref(0);
/** 开方是否可选医保0=仅自费) */
@@ -376,15 +408,20 @@ getPatientListByReception();
// 每5秒更新数据
setInterval(getPatientListByReception, 30_000);
// 药品分类
const categories = [
{label: '中药', value: 1},
{label: '中成(西)药', value: 2},
{label: '保健食品', value: 3},
{label: '产品服务包', value: 5},
{label: '非药品', value: 6},
{label: '医疗器械', value: 7},
];
// 药品分类(后端下发;失败时用本地兜底)
const categories = ref([
{label: '中药', value: 1, icon: '', icon_text: ''},
{label: '中成(西)药', value: 2, icon: '', icon_text: ''},
{label: '保健食品', value: 3, icon: '', icon_text: ''},
{label: '产品服务包', value: 5, icon: '', icon_text: ''},
{label: '非药品', value: 6, icon: '', icon_text: ''},
{label: '医疗器械', value: 7, icon: '', icon_text: ''},
]);
/** 后端默认处方类型 */
const prescriptionTypeDefault = ref(2);
/** 内容区左右滑切换 Tab */
const rxSwipeStartX = ref(0);
const rxSwipeStartY = ref(0);
// 当前状态
const activePatient = ref<null | Patient>(null);
@@ -601,6 +638,8 @@ const selectPatient = async (patient: Patient, isUpdateTabType = true) => {
activePatient.value = patient.user_patient;
const registerId = patient.id;
// 先拉类型,再恢复草稿/默认 Tab
await loadPrescriptionTypeOptions(registerId);
// 优先恢复完整草稿(诊断、医嘱、诊疗费、中药配置等)
const hasDraft = await restoreReceptionDraft(registerId);
if (!hasDraft) {
@@ -614,6 +653,9 @@ const selectPatient = async (patient: Patient, isUpdateTabType = true) => {
activeCategory.value = 1;
} else if (westData && JSON.parse(westData).length > 0) {
activeCategory.value = 2;
} else if (prescriptionTypeDefault.value) {
// 无草稿时使用后端下发的默认类型
activeCategory.value = prescriptionTypeDefault.value;
}
}
}
@@ -1011,7 +1053,7 @@ const showPrescription = () => {
tabType.value = 2;
};
// 监听tabType.value变化
// 监听tabType.value变化;进入开方面板时强制拉一次处方类型
watch(
() => tabType.value,
(newValue) => {
@@ -1022,6 +1064,10 @@ watch(
prescriptionList.value = value.prescription;
});
}
if (newValue === 2) {
const rid = getRegisterId() || selectPatientId.value || 0;
loadPrescriptionTypeOptions(Number(rid) || undefined);
}
newDrugInfo.value = {};
localStorage.setItem(
`doctorReception-type`,
@@ -1439,6 +1485,27 @@ function tabChange(id) {
if (id === 1) {
checkAndShowTransferTip();
}
focusActiveCategoryTab();
}
/** 记录左右滑起点 */
function onRxPanelPointerDown(e: PointerEvent) {
rxSwipeStartX.value = e.clientX;
rxSwipeStartY.value = e.clientY;
}
/** 左右滑切换相邻处方类型 */
function onRxPanelPointerUp(e: PointerEvent) {
const dx = e.clientX - rxSwipeStartX.value;
const dy = e.clientY - rxSwipeStartY.value;
if (Math.abs(dx) < 80 || Math.abs(dx) <= Math.abs(dy)) return;
const list = categories.value || [];
if (list.length < 2) return;
const idx = list.findIndex((c) => Number(c.value) === Number(activeCategory.value));
if (idx < 0) return;
const nextIdx = dx < 0 ? idx + 1 : idx - 1;
if (nextIdx < 0 || nextIdx >= list.length) return;
tabChange(list[nextIdx].value);
}
/**
@@ -2777,14 +2844,28 @@ watch(
<div class="drug-categories">
<button
v-for="category in categories"
:id="`rx-pc-tab-${category.value}`"
:key="category.value"
:class="{ active: activeCategory === category.value }"
@click="tabChange(category.value)"
>
{{ category.label }}
<img
v-if="category.icon"
class="tab-type-icon"
:src="category.icon"
alt=""
/>
<span>{{ category.label }}</span>
<span v-if="category.icon_text" class="tab-type-badge">{{ category.icon_text }}</span>
</button>
</div>
<div
class="rx-swipe-panel"
@pointerdown="onRxPanelPointerDown"
@pointerup="onRxPanelPointerUp"
>
<SpecialPrescriptionImportCard
v-if="hasSpecialPrescriptionRecord"
class="mt-5 max-w-xl"
@@ -2866,6 +2947,10 @@ watch(
</ImagePreviewGroup>
</div>
<!-- 已选药品列表 -->
<!-- 中药:顶部固定展示已选种数 -->
<div v-if="activeCategory === 1" class="mb-3 mt-2 text-base font-medium text-gray-700">
已选 {{ currentDrugs.length }} 种药
</div>
<!-- 中药 -->
<div v-if="activeCategory === 1" class="mb-5 mt-3 flex" style="flex-wrap: wrap; width: 100%;">
<Row>
@@ -3469,6 +3554,7 @@ watch(
保存为常用方
</Button>
</div>
</div>
</Page>
<div v-else-if="tabType === 0" class="prescription-panel">
<Empty/>
@@ -3650,6 +3736,9 @@ watch(
display: flex;
gap: 1rem;
margin: 1rem 0;
overflow-x: auto;
flex-wrap: nowrap;
padding-bottom: 4px;
}
.drug-categories button {
@@ -3657,12 +3746,36 @@ watch(
border: 1px solid #ddd;
border-radius: 10px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
white-space: nowrap;
}
.dark .drug-categories button {
border: 1px solid #333;
}
.tab-type-icon {
width: 16px;
height: 16px;
object-fit: contain;
}
.tab-type-badge {
font-size: 11px;
color: #e6a23c;
background: #fdf6ec;
padding: 0 6px;
border-radius: 4px;
line-height: 1.4;
}
.rx-swipe-panel {
touch-action: pan-y;
}
.drug-categories button.active {
color: #fff;
border-color: transparent;

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[60%]" title="绑定诊所">
<Modal class="w-[60%] h-[80%]" title="绑定诊所">
<div style="width: 80%; margin: 30px auto">
<StoreMultiSearch
v-if="data"

View File

@@ -55,7 +55,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[60%]" title="绑定诊所">
<Modal class="w-[60%] h-[80%]" title="绑定诊所">
<div style="width: 80%; margin: 30px auto">
<StoreMultiSearch
v-if="data"

View File

@@ -60,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="申请提现">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="申请提现">
<InvalidatedForm />
<SettlementForm />
</Modal>

View File

@@ -64,7 +64,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal :title="type === 1 ? '作废订单' : '结算订单'" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal :title="type === 1 ? '作废订单' : '结算订单'" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<InvalidatedForm v-if="type === 1" />
<SettlementForm v-else />
</Modal>

View File

@@ -60,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal :title="`提现审核【${type === 1 ? '拒绝' : '通过'}】`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal :title="`提现审核【${type === 1 ? '拒绝' : '通过'}】`" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<WithdrawalAuditForm />
</Modal>
</template>

View File

@@ -57,7 +57,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="编辑银行卡账户">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="编辑银行卡账户">
<Form />
</Modal>
</template>

View File

@@ -61,7 +61,7 @@ const setAmount = (amount: number) => {
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="申请提现">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="申请提现">
<Form />
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
<Button type="primary" @click="setAmount(1000)"> 1000 </Button>

View File

@@ -39,7 +39,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="Api访问日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="Api访问日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }"

View File

@@ -55,7 +55,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="监管回调详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="监管回调详情">
<Spin :spinning="loading">
<Descriptions bordered :column="2" class="mb-4">
<Descriptions.Item label="记录ID">{{ data.id }}</Descriptions.Item>

View File

@@ -30,7 +30,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="分账日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="分账日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"

View File

@@ -32,7 +32,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="Api访问日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="Api访问日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"

View File

@@ -30,7 +30,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="操作日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="操作日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"

View File

@@ -30,7 +30,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="订单日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="订单日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"

View File

@@ -30,7 +30,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="处方日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="处方日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"

View File

@@ -67,7 +67,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal title="拒绝审核" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal title="拒绝审核" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<Form />
</Modal>
</template>

View File

@@ -37,7 +37,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="处方溯源">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]" title="处方溯源">
<PrescriptionSourceContent :data="data" />
</Modal>
</template>

View File

@@ -51,7 +51,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`订单:【 ${orderNo} 】发货`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal :title="`订单:【 ${orderNo} 】发货`" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<Form />
</Modal>
</template>

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}平台`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}平台`" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<Form />
</Modal>
</template>

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}角色`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}角色`" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<Form />
</Modal>
</template>

View File

@@ -87,7 +87,7 @@ const rowImageSrc = (row: DrugItem) => {
</script>
<template>
<Modal title="修改药品价格" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal title="修改药品价格" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<div>
<Tabs v-model:activeKey="activeTab">
<TabPane

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}供应商`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}供应商`" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
<Form />
</Modal>
</template>