feat: 中药导入模块修复、部门财务的优化、队列的优化管理
This commit is contained in:
@@ -0,0 +1,658 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 中药药名气泡选择器
|
||||
* - 对齐诊断/医嘱 EntryKeywordBubble:Input 聚焦弹出 Teleport 浮层
|
||||
* - 远程搜索药品(getProductListDoctorReception),拼音/名称高亮
|
||||
* - 选中后回传完整商品行,供接诊/复诊/特色方/常用方复用现有落库逻辑
|
||||
*/
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import { LoadingOutlined, SearchOutlined } from '@ant-design/icons-vue';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { Empty, Input, Spin } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
rankByMatchScore,
|
||||
splitHighlight,
|
||||
type HighlightPart,
|
||||
} from '#/utils/dictSearchRank';
|
||||
import { getProductListDoctorReception } from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
export type ChineseDrugBubbleItem = {
|
||||
id: number;
|
||||
drug_id: number;
|
||||
price: number;
|
||||
buy_price?: number;
|
||||
drug: Record<string, any>;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 已选药名(展示回填;打开搜索时仍可编辑关键词) */
|
||||
displayName?: string;
|
||||
/** 药品类型:1 中药,颗粒等由调用方传入 */
|
||||
type?: number;
|
||||
storeId?: number;
|
||||
registerId?: number;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
/** 卡片内紧凑宽度 */
|
||||
compact?: boolean;
|
||||
/** 额外 class,便于焦点跳转 querySelector */
|
||||
inputClass?: string;
|
||||
}>(),
|
||||
{
|
||||
displayName: '',
|
||||
type: 1,
|
||||
storeId: 0,
|
||||
registerId: 0,
|
||||
placeholder: '药名',
|
||||
disabled: false,
|
||||
compact: false,
|
||||
inputClass: '',
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [item: ChineseDrugBubbleItem];
|
||||
}>();
|
||||
|
||||
const ROW_HEIGHT = 44;
|
||||
const HINT_HEIGHT = 34;
|
||||
const HEAD_HEIGHT = 32;
|
||||
const PANEL_PAD = 8;
|
||||
const PREFERRED_PANEL = 320;
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null);
|
||||
const panelRef = ref<HTMLElement | null>(null);
|
||||
const listRef = ref<HTMLElement | null>(null);
|
||||
const inputRef = ref<{ focus?: () => void } | null>(null);
|
||||
const keyword = ref('');
|
||||
const loading = ref(false);
|
||||
const open = ref(false);
|
||||
/** 是否处于「正在搜」:true 时不把 displayName 强写回 keyword */
|
||||
const searching = ref(false);
|
||||
const highlightIndex = ref(0);
|
||||
const options = ref<ChineseDrugBubbleItem[]>([]);
|
||||
const scrollTop = ref(0);
|
||||
const listViewportH = ref(200);
|
||||
const panelStyle = ref<Record<string, string>>({});
|
||||
|
||||
const totalHeight = computed(() => options.value.length * ROW_HEIGHT);
|
||||
const startIndex = computed(() =>
|
||||
Math.max(0, Math.floor(scrollTop.value / ROW_HEIGHT) - 2),
|
||||
);
|
||||
const endIndex = computed(() => {
|
||||
const visible = Math.ceil(listViewportH.value / ROW_HEIGHT) + 4;
|
||||
return Math.min(options.value.length - 1, startIndex.value + visible);
|
||||
});
|
||||
const visibleRows = computed(() => {
|
||||
const list = options.value;
|
||||
const start = startIndex.value;
|
||||
const end = endIndex.value;
|
||||
if (list.length === 0 || end < start) return [];
|
||||
return list.slice(start, end + 1).map((item, i) => ({
|
||||
item,
|
||||
index: start + i,
|
||||
}));
|
||||
});
|
||||
const offsetY = computed(() => startIndex.value * ROW_HEIGHT);
|
||||
|
||||
/**
|
||||
* 同步展示名:未打开搜索时用 displayName 回填输入框
|
||||
*/
|
||||
watch(
|
||||
() => props.displayName,
|
||||
(name) => {
|
||||
if (!searching.value && !open.value) {
|
||||
keyword.value = String(name || '');
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function partsOf(text: string): HighlightPart[] {
|
||||
return splitHighlight(text, keyword.value.trim());
|
||||
}
|
||||
|
||||
function drugNameOf(item: ChineseDrugBubbleItem): string {
|
||||
return String(item?.drug?.drug_name || item?.drug_name || '').trim();
|
||||
}
|
||||
|
||||
function unitLabelOf(item: ChineseDrugBubbleItem): string {
|
||||
const unit = item?.drug?.unit || item?.unit;
|
||||
if (typeof unit === 'object' && unit?.name) return String(unit.name);
|
||||
return String(item?.drug?.specification || item?.specification || '').trim() || '-';
|
||||
}
|
||||
|
||||
function priceLabelOf(item: ChineseDrugBubbleItem): string {
|
||||
const p = Number(item?.price ?? 0);
|
||||
return Number.isFinite(p) ? `¥${p.toFixed(2)}` : '-';
|
||||
}
|
||||
|
||||
/**
|
||||
* 面板 Teleport + fixed,避免接诊/Modal overflow 裁切
|
||||
*/
|
||||
function updatePanelPlacement() {
|
||||
const el = containerRef.value;
|
||||
if (!el) return;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight;
|
||||
const gap = 4;
|
||||
const spaceBelow = vh - rect.bottom - PANEL_PAD;
|
||||
const spaceAbove = rect.top - PANEL_PAD;
|
||||
const placeTop = spaceBelow < 180 && spaceAbove > spaceBelow;
|
||||
const avail = Math.max(140, placeTop ? spaceAbove : spaceBelow);
|
||||
const panelMax = Math.min(PREFERRED_PANEL, avail);
|
||||
listViewportH.value = Math.max(80, panelMax - HINT_HEIGHT - HEAD_HEIGHT);
|
||||
|
||||
const PANEL_MIN_WIDTH = 420;
|
||||
const width = Math.min(
|
||||
Math.max(rect.width, PANEL_MIN_WIDTH),
|
||||
vw - PANEL_PAD * 2,
|
||||
);
|
||||
let left = rect.left;
|
||||
if (left + width > vw - PANEL_PAD) {
|
||||
left = vw - PANEL_PAD - width;
|
||||
}
|
||||
if (left < PANEL_PAD) left = PANEL_PAD;
|
||||
|
||||
const base: Record<string, string> = {
|
||||
position: 'fixed',
|
||||
zIndex: '4000',
|
||||
width: `${width}px`,
|
||||
maxHeight: `${panelMax}px`,
|
||||
left: `${left}px`,
|
||||
right: 'auto',
|
||||
};
|
||||
if (placeTop) {
|
||||
panelStyle.value = {
|
||||
...base,
|
||||
top: 'auto',
|
||||
bottom: `${Math.max(PANEL_PAD, vh - rect.top + gap)}px`,
|
||||
};
|
||||
} else {
|
||||
panelStyle.value = {
|
||||
...base,
|
||||
top: `${rect.bottom + gap}px`,
|
||||
bottom: 'auto',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程搜药;空关键词不请求(与原中药 Select 一致)
|
||||
*/
|
||||
async function fetchDrugs(kw: string) {
|
||||
const name = String(kw || '').trim();
|
||||
if (!name) {
|
||||
options.value = [];
|
||||
loading.value = false;
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getProductListDoctorReception({
|
||||
name,
|
||||
type: props.type,
|
||||
store_id: props.storeId || 0,
|
||||
...(props.registerId > 0 ? { register_id: props.registerId } : {}),
|
||||
});
|
||||
const list = Array.isArray(res) ? res : [];
|
||||
const mapped: ChineseDrugBubbleItem[] = list.map((item: any) => {
|
||||
const drug = item.drug || {};
|
||||
return {
|
||||
...item,
|
||||
id: Number(item.id || 0),
|
||||
drug_id: Number(item.drug_id || drug.id || 0),
|
||||
price: Number(item.price ?? 0),
|
||||
buy_price: item.buy_price,
|
||||
drug: {
|
||||
...drug,
|
||||
drug_name: drug.drug_name || item.drug_name || '',
|
||||
},
|
||||
};
|
||||
});
|
||||
options.value = rankByMatchScore(
|
||||
mapped,
|
||||
name,
|
||||
(it) => drugNameOf(it),
|
||||
(it) =>
|
||||
[
|
||||
it.drug?.pinyin,
|
||||
it.drug?.pinyin_full,
|
||||
it.drug?.initials_of_pinyin,
|
||||
it.drug?.specification,
|
||||
]
|
||||
.map((v) => String(v || ''))
|
||||
.join(' '),
|
||||
);
|
||||
highlightIndex.value = options.value.length > 0 ? 0 : -1;
|
||||
scrollTop.value = 0;
|
||||
if (listRef.value) listRef.value.scrollTop = 0;
|
||||
} catch (e) {
|
||||
console.error('中药药名搜索失败:', e);
|
||||
options.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
nextTick(() => updatePanelPlacement());
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedFetch = useDebounceFn((kw: string) => {
|
||||
void fetchDrugs(kw);
|
||||
}, 280);
|
||||
|
||||
function openPanel() {
|
||||
if (props.disabled) return;
|
||||
open.value = true;
|
||||
searching.value = true;
|
||||
nextTick(() => {
|
||||
updatePanelPlacement();
|
||||
if (keyword.value.trim()) {
|
||||
debouncedFetch(keyword.value);
|
||||
} else {
|
||||
options.value = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closePanel(restoreDisplay = true) {
|
||||
open.value = false;
|
||||
searching.value = false;
|
||||
options.value = [];
|
||||
highlightIndex.value = 0;
|
||||
if (restoreDisplay) {
|
||||
keyword.value = String(props.displayName || '');
|
||||
}
|
||||
}
|
||||
|
||||
function handleFocus() {
|
||||
if (props.disabled) return;
|
||||
// 聚焦时清空为搜索态,便于直接输入新药名
|
||||
if (props.displayName && keyword.value === props.displayName) {
|
||||
keyword.value = '';
|
||||
}
|
||||
openPanel();
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
searching.value = true;
|
||||
if (!open.value) openPanel();
|
||||
else updatePanelPlacement();
|
||||
debouncedFetch(keyword.value);
|
||||
}
|
||||
|
||||
function selectItem(item: ChineseDrugBubbleItem) {
|
||||
const name = drugNameOf(item);
|
||||
keyword.value = name;
|
||||
searching.value = false;
|
||||
open.value = false;
|
||||
options.value = [];
|
||||
emit('select', item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 键盘高亮循环移动(与诊断 EntryKeywordBubble 一致:第一项再上键 → 最后一项)
|
||||
*/
|
||||
function moveHighlight(delta: number) {
|
||||
if (options.value.length === 0) return;
|
||||
const len = options.value.length;
|
||||
let next = highlightIndex.value;
|
||||
if (next < 0) next = 0;
|
||||
else next = (next + delta + len) % len;
|
||||
highlightIndex.value = next;
|
||||
ensureRowVisible(highlightIndex.value);
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (!open.value) {
|
||||
if (e.key === 'ArrowDown' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
openPanel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
closePanel(true);
|
||||
return;
|
||||
}
|
||||
if (options.value.length === 0) return;
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
||||
e.preventDefault();
|
||||
moveHighlight(1);
|
||||
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
|
||||
e.preventDefault();
|
||||
moveHighlight(-1);
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const hit = options.value[highlightIndex.value];
|
||||
if (hit) selectItem(hit);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureRowVisible(index: number) {
|
||||
const el = listRef.value;
|
||||
if (!el || index < 0) return;
|
||||
const top = index * ROW_HEIGHT;
|
||||
const bottom = top + ROW_HEIGHT;
|
||||
if (top < el.scrollTop) el.scrollTop = top;
|
||||
else if (bottom > el.scrollTop + el.clientHeight) {
|
||||
el.scrollTop = bottom - el.clientHeight;
|
||||
}
|
||||
// 同步虚拟列表切片(循环跳到首/尾时必须刷新可见区)
|
||||
scrollTop.value = el.scrollTop;
|
||||
}
|
||||
|
||||
function onListScroll() {
|
||||
scrollTop.value = listRef.value?.scrollTop || 0;
|
||||
}
|
||||
|
||||
function onDocPointerDown(e: MouseEvent) {
|
||||
if (!open.value) return;
|
||||
const t = e.target as Node;
|
||||
if (containerRef.value?.contains(t)) return;
|
||||
if (panelRef.value?.contains(t)) return;
|
||||
closePanel(true);
|
||||
}
|
||||
|
||||
function onWinChange() {
|
||||
if (open.value) updatePanelPlacement();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('mousedown', onDocPointerDown, true);
|
||||
window.addEventListener('resize', onWinChange);
|
||||
window.addEventListener('scroll', onWinChange, true);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('mousedown', onDocPointerDown, true);
|
||||
window.removeEventListener('resize', onWinChange);
|
||||
window.removeEventListener('scroll', onWinChange, true);
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
focus() {
|
||||
nextTick(() => {
|
||||
const el = inputRef.value as any;
|
||||
el?.focus?.();
|
||||
const native = containerRef.value?.querySelector(
|
||||
'input',
|
||||
) as HTMLInputElement | null;
|
||||
native?.focus();
|
||||
});
|
||||
},
|
||||
close() {
|
||||
closePanel(true);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="chinese-drug-name-bubble"
|
||||
:class="[{ 'chinese-drug-name-bubble--compact': compact }, inputClass]"
|
||||
>
|
||||
<Input
|
||||
ref="inputRef"
|
||||
v-model:value="keyword"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
allow-clear
|
||||
size="small"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@keydown="handleKeydown"
|
||||
>
|
||||
<template #prefix>
|
||||
<LoadingOutlined v-if="loading" class="text-muted-foreground" />
|
||||
<SearchOutlined v-else class="text-muted-foreground" />
|
||||
</template>
|
||||
</Input>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="open"
|
||||
ref="panelRef"
|
||||
class="chinese-drug-name-bubble__panel"
|
||||
:style="panelStyle"
|
||||
>
|
||||
<div class="chinese-drug-name-bubble__hint">
|
||||
<span>↑↓←→ 循环切换 · Enter 选用 · Esc 关闭</span>
|
||||
<span class="chinese-drug-name-bubble__count">
|
||||
<template v-if="keyword.trim()">搜到 {{ options.length }} 条</template>
|
||||
<template v-else>输入药名搜索</template>
|
||||
</span>
|
||||
</div>
|
||||
<Spin :spinning="loading">
|
||||
<Empty
|
||||
v-if="!loading && options.length === 0"
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
:description="
|
||||
keyword.trim() ? '未找到匹配药材' : '请输入药名/拼音搜索'
|
||||
"
|
||||
class="py-4"
|
||||
/>
|
||||
<template v-else>
|
||||
<div
|
||||
class="chinese-drug-name-bubble__head"
|
||||
:class="{ 'has-score': !!keyword.trim() }"
|
||||
>
|
||||
<span class="col-name">药名</span>
|
||||
<span class="col-spec">规格/单位</span>
|
||||
<span class="col-price">价格</span>
|
||||
<span v-if="keyword.trim()" class="col-score">匹配度</span>
|
||||
</div>
|
||||
<div
|
||||
ref="listRef"
|
||||
class="chinese-drug-name-bubble__list"
|
||||
:style="{ height: `${listViewportH}px` }"
|
||||
@scroll="onListScroll"
|
||||
>
|
||||
<div
|
||||
class="chinese-drug-name-bubble__phantom"
|
||||
:style="{ height: `${totalHeight}px` }"
|
||||
/>
|
||||
<div
|
||||
class="chinese-drug-name-bubble__virtual"
|
||||
:style="{ transform: `translateY(${offsetY}px)` }"
|
||||
>
|
||||
<div
|
||||
v-for="{ item, index } in visibleRows"
|
||||
:key="`${item.drug_id}_${index}`"
|
||||
class="chinese-drug-name-bubble__row"
|
||||
:class="{
|
||||
'is-active': index === highlightIndex,
|
||||
'has-score': !!keyword.trim(),
|
||||
}"
|
||||
:style="{ height: `${ROW_HEIGHT}px` }"
|
||||
@mousedown.prevent="selectItem(item)"
|
||||
@mouseenter="highlightIndex = index"
|
||||
>
|
||||
<span class="col-name" :title="drugNameOf(item)">
|
||||
<template
|
||||
v-for="(p, pi) in partsOf(drugNameOf(item))"
|
||||
:key="`n_${pi}`"
|
||||
>
|
||||
<em v-if="p.hit" class="hit">{{ p.text }}</em>
|
||||
<template v-else>{{ p.text }}</template>
|
||||
</template>
|
||||
</span>
|
||||
<span class="col-spec" :title="unitLabelOf(item)">
|
||||
{{ unitLabelOf(item) }}
|
||||
</span>
|
||||
<span class="col-price">{{ priceLabelOf(item) }}</span>
|
||||
<span v-if="keyword.trim()" class="col-score">
|
||||
{{ Number(item.match_score || 0) }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Spin>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.chinese-drug-name-bubble {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
box-sizing: border-box;
|
||||
/* 锁死宽度:避免接诊/复诊/特色方/常用方 flex 布局把药名框撑满整行 */
|
||||
width: 100px !important;
|
||||
min-width: 88px !important;
|
||||
max-width: 110px !important;
|
||||
flex: 0 0 100px !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
.chinese-drug-name-bubble--compact {
|
||||
width: 100px !important;
|
||||
min-width: 88px !important;
|
||||
max-width: 110px !important;
|
||||
flex: 0 0 100px !important;
|
||||
}
|
||||
.chinese-drug-name-bubble :deep(.ant-input-affix-wrapper) {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
padding-inline: 6px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.chinese-drug-name-bubble :deep(.ant-input-prefix) {
|
||||
margin-inline-end: 2px;
|
||||
}
|
||||
.chinese-drug-name-bubble :deep(.ant-input) {
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.chinese-drug-name-bubble__panel {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 8px;
|
||||
border: 1px solid hsl(var(--border));
|
||||
background: hsl(var(--popover, var(--background)));
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
.chinese-drug-name-bubble__hint {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
background: hsl(var(--muted) / 0.35);
|
||||
}
|
||||
.chinese-drug-name-bubble__count {
|
||||
flex-shrink: 0;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
.chinese-drug-name-bubble__list {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.chinese-drug-name-bubble__phantom {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
.chinese-drug-name-bubble__virtual {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.chinese-drug-name-bubble__head,
|
||||
.chinese-drug-name-bubble__row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 100px 72px;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.chinese-drug-name-bubble__head.has-score,
|
||||
.chinese-drug-name-bubble__row.has-score {
|
||||
grid-template-columns: 1fr 90px 64px 48px;
|
||||
}
|
||||
.chinese-drug-name-bubble__head {
|
||||
flex-shrink: 0;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--muted-foreground));
|
||||
background: hsl(var(--popover, var(--background)));
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
.chinese-drug-name-bubble__row {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid hsl(var(--border) / 0.6);
|
||||
}
|
||||
.chinese-drug-name-bubble__row:hover,
|
||||
.chinese-drug-name-bubble__row.is-active {
|
||||
background: hsl(var(--accent));
|
||||
color: hsl(var(--accent-foreground));
|
||||
}
|
||||
.col-name,
|
||||
.col-spec {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.col-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
.col-spec {
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
.col-price {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.col-score {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 600;
|
||||
color: #d4380d;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chinese-drug-name-bubble__row.is-active .col-spec {
|
||||
color: inherit;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.chinese-drug-name-bubble__row.is-active .col-score {
|
||||
color: inherit;
|
||||
}
|
||||
.hit {
|
||||
font-style: normal;
|
||||
color: #cf1322;
|
||||
background: rgba(255, 214, 102, 0.55);
|
||||
border-radius: 2px;
|
||||
padding: 0 1px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 中药药名气泡选择器
|
||||
*/
|
||||
export { default as ChineseDrugNameBubble } from './chinese-drug-name-bubble.vue';
|
||||
export type { ChineseDrugBubbleItem } from './chinese-drug-name-bubble.vue';
|
||||
@@ -105,7 +105,9 @@ const downloadQRCode = async (name: string, isQr = false) => {
|
||||
{{ doctorInfo.doctor.name }} {{ doctorInfo.doctor.title.name }}
|
||||
</div>
|
||||
<div class="qrAddress">
|
||||
<span class="addressTitle">诊所地址:</span>{{ previewAddress }}
|
||||
<span class="addressTitle">{{
|
||||
Number(doctorInfo?.store?.type) === 1 ? '药店地址:' : '诊所地址:'
|
||||
}}</span>{{ previewAddress }}
|
||||
</div>
|
||||
<Image :preview="false" :src="doctorInfo.qr_code" class="mt-3" height="30" width="30" />
|
||||
<h3 class="border-l-4 border-blue-500 pl-3 text-lg font-semibold">
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
/**
|
||||
* 诊所/药店二维码预览弹窗
|
||||
* 按门店 type(0 诊所 / 1 药店)切换「诊所|药店」文案,列表页与门店卡片共用
|
||||
*/
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
@@ -11,6 +15,13 @@ const htmlToImage = ref('');
|
||||
const url = ref('');
|
||||
const previewTitle = ref('二维码');
|
||||
const previewAddress = ref('');
|
||||
/** 门店类型:0=诊所 1=药店,未传时按诊所示 */
|
||||
const storeType = ref(0);
|
||||
|
||||
const isPharmacy = computed(() => Number(storeType.value) === 1);
|
||||
const kindLabel = computed(() => (isPharmacy.value ? '药店' : '诊所'));
|
||||
const addressLabel = computed(() => `${kindLabel.value}地址:`);
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
footer: false,
|
||||
@@ -28,21 +39,28 @@ const [Modal, modalApi] = useVbenModal({
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
const { values, title, address } =
|
||||
modalApi.getData<Record<string, any>>();
|
||||
const { values, title, address, type, store_type } =
|
||||
modalApi.getData<Record<string, any>>() || {};
|
||||
if (values) {
|
||||
url.value = values;
|
||||
qrCodeUrl.value = values;
|
||||
previewTitle.value = title;
|
||||
previewAddress.value = address;
|
||||
// 兼容 type / store_type 两种传参
|
||||
storeType.value = Number(type ?? store_type ?? 0);
|
||||
modalApi.setState({
|
||||
confirmText: `保存${kindLabel.value}卡片图片`,
|
||||
cancelText: `保存${kindLabel.value}二维码图片`,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 下载二维码
|
||||
* @param name
|
||||
* @param isQr
|
||||
* 下载二维码卡片或纯二维码图片
|
||||
* @param name 文件名后缀
|
||||
* @param isQr true=只截二维码区域,false=截整张卡片
|
||||
*/
|
||||
const downloadQRCode = async (name: string, isQr = false) => {
|
||||
const element = isQr ? qrCodeUrl.value : htmlToImage.value;
|
||||
@@ -51,12 +69,11 @@ const downloadQRCode = async (name: string, isQr = false) => {
|
||||
allowTaint: true,
|
||||
logging: false,
|
||||
}).then((canvas) => {
|
||||
// 创建a标签下载
|
||||
const link = document.createElement('a'); // 创建a标签
|
||||
link.href = canvas.toDataURL(); // 是canvas对象的一种方法,用于将canvas对象转换为base64位编码
|
||||
link.setAttribute('download', `${previewTitle.value}${name}.png`); // 利用了a标签的download 来下载 canvas图片
|
||||
link.style.display = 'none'; // 将图片隐藏起来
|
||||
document.body.append(link); // 插入到其中
|
||||
const link = document.createElement('a');
|
||||
link.href = canvas.toDataURL();
|
||||
link.setAttribute('download', `${previewTitle.value}${name}.png`);
|
||||
link.style.display = 'none';
|
||||
document.body.append(link);
|
||||
link.click();
|
||||
});
|
||||
};
|
||||
@@ -69,7 +86,7 @@ const downloadQRCode = async (name: string, isQr = false) => {
|
||||
<div class="qrTitle">{{ previewTitle }}</div>
|
||||
<Image :preview="false" :src="url" height="30" width="30" />
|
||||
<div class="qrAddress">
|
||||
<span class="addressTitle">诊所地址:</span>{{ previewAddress }}
|
||||
<span class="addressTitle">{{ addressLabel }}</span>{{ previewAddress }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,10 +71,13 @@ const stats = computed(() => cardData.value?.stats_summary ?? {});
|
||||
const doctorList = computed(() => cardData.value?.doctor_list ?? []);
|
||||
const isClinic = computed(() => Number(store.value?.type) === 0);
|
||||
const isPharmacy = computed(() => Number(store.value?.type) === 1);
|
||||
/** 按门店类型区分诊所/药店文案,避免药店仍显示「诊所」 */
|
||||
const storeKindLabel = computed(() => (isPharmacy.value ? '药店' : '诊所'));
|
||||
const qrTabLabel = computed(() => `${storeKindLabel.value}二维码`);
|
||||
const modalTitle = computed(() => {
|
||||
const name = store.value?.name;
|
||||
if (!name) return '门店详情';
|
||||
return isPharmacy.value ? `药店详情 · ${name}` : `诊所详情 · ${name}`;
|
||||
return `${storeKindLabel.value}详情 · ${name}`;
|
||||
});
|
||||
|
||||
const drugTypeOptions = [
|
||||
@@ -479,7 +482,7 @@ function clinicTypeText(v: number) {
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane key="qr" tab="门店二维码">
|
||||
<Tabs.TabPane key="qr" :tab="qrTabLabel">
|
||||
<div class="flex flex-col items-center gap-4 py-6 pl-4">
|
||||
<template v-if="cardData?.qr_code || store.qr_code">
|
||||
<Image
|
||||
@@ -488,13 +491,13 @@ function clinicTypeText(v: number) {
|
||||
:preview="true"
|
||||
/>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{{ store.name }} 门店二维码
|
||||
{{ store.name }} {{ qrTabLabel }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Empty description="尚未生成二维码" />
|
||||
<Empty :description="`尚未生成${storeKindLabel}二维码`" />
|
||||
<Button type="primary" :loading="generatingQr" @click="ensureQrCode">
|
||||
生成二维码
|
||||
生成{{ storeKindLabel }}二维码
|
||||
</Button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -94,6 +94,8 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
const newDrugInfo = ref<any>({});
|
||||
const selectChineseIndex = ref(-1);
|
||||
const selectChineseId = ref(0);
|
||||
/** 中药气泡选中的完整商品行(添加时优先使用) */
|
||||
const pendingNewChineseProduct = ref<any>(null);
|
||||
|
||||
// 二次签名相关
|
||||
const doctorSecondSign = ref(0);
|
||||
@@ -726,7 +728,91 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
updateCurrentDrugs(newDrugs);
|
||||
};
|
||||
|
||||
// 新药品操作
|
||||
/**
|
||||
* 中药气泡:选中新增行药名
|
||||
* @param item 商品检索完整行
|
||||
*/
|
||||
const onSelectNewChineseDrug = (item: any) => {
|
||||
const drugId = Number(item?.drug_id || item?.drug?.id || 0);
|
||||
if (!drugId) return;
|
||||
const check = currentDrugs.value.find((v) => Number(v.id) === drugId);
|
||||
if (check) {
|
||||
newDrugInfo.value.id = '';
|
||||
newDrugInfo.value.name = '';
|
||||
pendingNewChineseProduct.value = null;
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
pendingNewChineseProduct.value = item;
|
||||
newDrugInfo.value.id = drugId;
|
||||
newDrugInfo.value.price = item.price;
|
||||
newDrugInfo.value.name = item.drug?.drug_name || '';
|
||||
newDrugInfo.value.unit =
|
||||
item.drug?.unit ||
|
||||
drugUnit.value.find((u) => u.id === item.drug?.unit_id);
|
||||
newDrugInfo.value.unit_id = item.drug?.unit_id;
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(
|
||||
'.new-number-input input',
|
||||
) as HTMLElement;
|
||||
input?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 中药气泡:替换已选行药名
|
||||
* @param index 行下标
|
||||
* @param item 商品检索完整行
|
||||
*/
|
||||
const onSelectOldChineseDrug = (index: number, item: any) => {
|
||||
const drugId = Number(item?.drug?.id || item?.drug_id || 0);
|
||||
if (!drugId) return;
|
||||
const check = currentDrugs.value.find(
|
||||
(v, i) => i !== index && Number(v.id) === drugId,
|
||||
);
|
||||
if (check) {
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
const data = item;
|
||||
const newProduct = {
|
||||
index_id: data.id,
|
||||
id: data.drug.id,
|
||||
drug_name: data.drug.drug_name,
|
||||
number: data.drug.number,
|
||||
use_num: drugTime.value.find((u) => u.id === data.drug.time_id),
|
||||
use_type: drugUseType.value.find((u) => u.id === data.drug.type_id),
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(u) => u.id === data.drug.frequency_id,
|
||||
),
|
||||
unit:
|
||||
data.drug?.unit ||
|
||||
drugUnit.value.find((u) => u.id === data.drug.unit_id),
|
||||
price: data.price,
|
||||
buy_price: data.buy_price,
|
||||
way_id: data.drug?.way_id,
|
||||
use_ways: drugUseWay.value.find((u) => u.id === data.drug.way_id),
|
||||
time_id: data.drug.time_id,
|
||||
type_id: data.drug.type_id,
|
||||
frequency_id: data.drug.frequency_id,
|
||||
unit_id: data.drug.unit_id,
|
||||
image: data.drug.image,
|
||||
specification: data.drug?.specification || data.specification || '',
|
||||
instruction: data.drug.instruction,
|
||||
type: data.drug.type,
|
||||
};
|
||||
const newDrugs = [...currentDrugs.value];
|
||||
newDrugs[index] = newProduct;
|
||||
updateCurrentDrugs(newDrugs);
|
||||
nextTick(() => {
|
||||
const input = document.querySelector(
|
||||
`.old-number-input-${index} input`,
|
||||
) as HTMLElement;
|
||||
input?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
// 新药品操作(Select 兼容)
|
||||
const selectNewDrugInfo = () => {
|
||||
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id);
|
||||
if (check) {
|
||||
@@ -737,6 +823,7 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
|
||||
const data = drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
|
||||
if (data) {
|
||||
pendingNewChineseProduct.value = data;
|
||||
newDrugInfo.value.price = data.price;
|
||||
newDrugInfo.value.name = data.drug.drug_name;
|
||||
// 选药后立刻带上真实单位,供新行数量后缀展示
|
||||
@@ -822,7 +909,9 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
|
||||
const data =
|
||||
pendingNewChineseProduct.value ||
|
||||
drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
|
||||
if (!data) {
|
||||
message.error('请选择药品');
|
||||
return false;
|
||||
@@ -833,6 +922,7 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
const success = addProducts(data);
|
||||
if (success) {
|
||||
newDrugInfo.value = {};
|
||||
pendingNewChineseProduct.value = null;
|
||||
}
|
||||
return success;
|
||||
};
|
||||
@@ -1323,6 +1413,8 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
||||
updateDrugUsage,
|
||||
selectNewDrugInfo,
|
||||
selectOldDrugInfo,
|
||||
onSelectNewChineseDrug,
|
||||
onSelectOldChineseDrug,
|
||||
setSelectChineseIndex,
|
||||
addDrugByChinese,
|
||||
selectDrugByNewDrugInfo,
|
||||
|
||||
@@ -68,6 +68,7 @@ import GoldenFormulaModal from '#/views/doctor/doctor-reception/components/Golde
|
||||
import InfoModal from '#/components/modal/InfoModal.vue';
|
||||
// 药品搜索选择组件
|
||||
import { DrugSearchSelect } from '#/components/drug-search-select';
|
||||
import { ChineseDrugNameBubble } from '#/components/chinese-drug-name-bubble';
|
||||
import { formatPriceDisplay } from '#/utils/formatPrice';
|
||||
import ChineseMedicineConfig from '#/views/doctor/doctor-reception/components/ChineseMedicineConfig.vue';
|
||||
import { getDeliveryWarehouseOptionsByDrugs, getPrescriptionInfoApi } from '#/views/doctor/doctor-reception/api';
|
||||
@@ -1408,69 +1409,53 @@ const cancelSaveCommonPrescription = () => {
|
||||
<div>
|
||||
<span class="card-index">{{ index + 1 }}、</span>
|
||||
<p>
|
||||
<Select
|
||||
v-model:value="drug.id"
|
||||
:filter-option="false"
|
||||
class="old-select-drug-name w-full mb-2"
|
||||
<ChineseDrugNameBubble
|
||||
:display-name="drug.drug_name"
|
||||
:store-id="prescriptionStore.myStoreId"
|
||||
:register-id="Number(prescriptionStore.currentRegisterId || 0)"
|
||||
:type="1"
|
||||
compact
|
||||
input-class="old-select-drug-name"
|
||||
placeholder="药名"
|
||||
show-search
|
||||
@change="prescriptionStore.selectOldDrugInfo"
|
||||
@dropdown-visible-change="
|
||||
prescriptionStore.setSelectChineseIndex(index, drug.id)
|
||||
@select="
|
||||
(item) =>
|
||||
prescriptionStore.onSelectOldChineseDrug(index, item)
|
||||
"
|
||||
@search="searchOption"
|
||||
/>
|
||||
,
|
||||
<InputNumber
|
||||
v-model:value="drug.number"
|
||||
:class="`old-number-input-${index}`"
|
||||
:controls="false"
|
||||
style="width: 80px; min-width: 80px"
|
||||
@blur="prescriptionStore.updateChineseNumber"
|
||||
@keydown="
|
||||
prescriptionStore.updateChineseNumberGoNewDrug($event)
|
||||
"
|
||||
>
|
||||
<template #addonAfter>{{ drug.unit?.name || 'g' }}</template>
|
||||
</InputNumber>
|
||||
,
|
||||
<Select
|
||||
:value="drug?.way_id"
|
||||
placeholder="用法"
|
||||
style="width: 88px; min-width: 72px"
|
||||
@change="prescriptionStore.selectDrugUseWayChange"
|
||||
@dropdown-visible-change="
|
||||
prescriptionStore.selectProductChange(drug.index_id)
|
||||
"
|
||||
>
|
||||
<SelectOption :value="0">煎服</SelectOption>
|
||||
<SelectOption
|
||||
v-if="prescriptionStore.drugList.length === 0"
|
||||
:value="drug.id"
|
||||
v-for="(
|
||||
value, wayIndex
|
||||
) in prescriptionStore.drugUseWay"
|
||||
:key="wayIndex"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ drug.drug_name }}
|
||||
</SelectOption>
|
||||
<SelectOption
|
||||
v-for="(value, drugIndex) in prescriptionStore.drugList"
|
||||
v-else
|
||||
:key="drugIndex"
|
||||
:value="value.drug.id"
|
||||
>
|
||||
{{ value.drug.drug_name }}
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
|
||||
<div class="flex gap-1 items-center">
|
||||
<InputNumber
|
||||
v-model:value="drug.number"
|
||||
:class="`old-number-input-${index}`"
|
||||
:controls="false"
|
||||
style="width: 80px"
|
||||
@blur="prescriptionStore.updateChineseNumber"
|
||||
@keydown="
|
||||
prescriptionStore.updateChineseNumberGoNewDrug($event)
|
||||
"
|
||||
>
|
||||
<template #addonAfter>{{ drug.unit?.name || 'g' }}</template>
|
||||
</InputNumber>
|
||||
|
||||
<Select
|
||||
:value="drug?.way_id"
|
||||
placeholder="用法"
|
||||
style="flex:1"
|
||||
@change="prescriptionStore.selectDrugUseWayChange"
|
||||
@dropdown-visible-change="
|
||||
prescriptionStore.selectProductChange(drug.index_id)
|
||||
"
|
||||
>
|
||||
<SelectOption :value="0">煎服</SelectOption>
|
||||
<SelectOption
|
||||
v-for="(
|
||||
value, wayIndex
|
||||
) in prescriptionStore.drugUseWay"
|
||||
:key="wayIndex"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
</p>
|
||||
<p class="card-price">
|
||||
¥<span>{{ (drug.price * drug.number).toFixed(2) }}</span>
|
||||
@@ -1498,58 +1483,49 @@ const cancelSaveCommonPrescription = () => {
|
||||
<div>
|
||||
<span class="card-index">{{ prescriptionStore.currentDrugs.length + 1 }}、</span>
|
||||
<div>
|
||||
<Select
|
||||
v-model:value="prescriptionStore.newDrugInfo.id"
|
||||
:filter-option="false"
|
||||
class="new-select-drug-name w-full mb-2"
|
||||
<ChineseDrugNameBubble
|
||||
:display-name="prescriptionStore.newDrugInfo.name || ''"
|
||||
:store-id="prescriptionStore.myStoreId"
|
||||
:register-id="Number(prescriptionStore.currentRegisterId || 0)"
|
||||
:type="1"
|
||||
compact
|
||||
input-class="new-select-drug-name"
|
||||
placeholder="药名"
|
||||
show-search
|
||||
@change="prescriptionStore.selectNewDrugInfo"
|
||||
@search="searchOption"
|
||||
@select="prescriptionStore.onSelectNewChineseDrug"
|
||||
/>
|
||||
,
|
||||
<InputNumber
|
||||
v-model:value="prescriptionStore.newDrugInfo.number"
|
||||
class="new-number-input"
|
||||
style="width: 80px; min-width: 80px"
|
||||
@blur="newDrugBlur"
|
||||
@keydown="
|
||||
prescriptionStore.selectDrugByNewDrugInfo($event, true)
|
||||
"
|
||||
>
|
||||
<template #addonAfter>{{ prescriptionStore.newDrugInfo.unit?.name || 'g' }}</template>
|
||||
</InputNumber>
|
||||
,
|
||||
<Select
|
||||
v-model:value="prescriptionStore.newDrugInfo.way_id"
|
||||
placeholder="用法"
|
||||
style="width: 88px; min-width: 72px"
|
||||
@keydown="
|
||||
prescriptionStore.selectDrugByNewDrugInfo($event, true)
|
||||
"
|
||||
>
|
||||
<SelectOption :value="0">煎服</SelectOption>
|
||||
<SelectOption
|
||||
v-for="(value, drugIndex) in prescriptionStore.drugList"
|
||||
:key="drugIndex"
|
||||
:value="value.drug_id"
|
||||
v-for="(
|
||||
value, wayIndex
|
||||
) in prescriptionStore.drugUseWay"
|
||||
:key="wayIndex"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.drug.drug_name }}
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
|
||||
<div class="flex gap-1 items-center">
|
||||
<InputNumber
|
||||
v-model:value="prescriptionStore.newDrugInfo.number"
|
||||
class="new-number-input"
|
||||
style="width: 80px"
|
||||
@blur="newDrugBlur"
|
||||
@keydown="
|
||||
prescriptionStore.selectDrugByNewDrugInfo($event, true)
|
||||
"
|
||||
>
|
||||
<template #addonAfter>{{ prescriptionStore.newDrugInfo.unit?.name || 'g' }}</template>
|
||||
</InputNumber>
|
||||
|
||||
<Select
|
||||
v-model:value="prescriptionStore.newDrugInfo.way_id"
|
||||
placeholder="用法"
|
||||
style="flex:1"
|
||||
@keydown="
|
||||
prescriptionStore.selectDrugByNewDrugInfo($event, true)
|
||||
"
|
||||
>
|
||||
<SelectOption :value="0">煎服</SelectOption>
|
||||
<SelectOption
|
||||
v-for="(
|
||||
value, wayIndex
|
||||
) in prescriptionStore.drugUseWay"
|
||||
:key="wayIndex"
|
||||
:value="value.id"
|
||||
>
|
||||
{{ value.name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<p class="card-price">
|
||||
¥<span>{{
|
||||
(
|
||||
@@ -2122,6 +2098,18 @@ const cancelSaveCommonPrescription = () => {
|
||||
position: relative;
|
||||
min-height: 120px;
|
||||
}
|
||||
/* 中药卡片:药名/克数/用法同一行,药名气泡锁宽 */
|
||||
.prescription-card :deep(.chinese-drug-name-bubble) {
|
||||
width: 100px !important;
|
||||
min-width: 88px !important;
|
||||
max-width: 110px !important;
|
||||
flex: 0 0 100px !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.prescription-card > div > p,
|
||||
.prescription-card > div > div {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.drug-categories {
|
||||
display: flex;
|
||||
|
||||
@@ -15,12 +15,9 @@ import {
|
||||
Select,
|
||||
SelectOption,
|
||||
} from 'ant-design-vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import {
|
||||
getDrugUseList,
|
||||
getProductListDoctorReception,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
import { ChineseDrugNameBubble } from '#/components/chinese-drug-name-bubble';
|
||||
import { getDrugUseList } from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
import PrescriptionDiagnosisOrderCards from './PrescriptionDiagnosisOrderCards.vue';
|
||||
|
||||
@@ -118,7 +115,6 @@ const doctorOrderModel = computed({
|
||||
});
|
||||
|
||||
const drugUseWay = ref<any[]>([]);
|
||||
const chineseSearchResults = ref<any[]>([]);
|
||||
const newDrugInfo = ref<{
|
||||
id?: number;
|
||||
name?: string;
|
||||
@@ -151,48 +147,36 @@ function syncToParent() {
|
||||
emit('update:dayDosage', dayDosageLocal.value);
|
||||
}
|
||||
|
||||
const searchChineseDrugs = debounce(async (keyword: string) => {
|
||||
if (!keyword || keyword.length < 1) {
|
||||
chineseSearchResults.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await getProductListDoctorReception({
|
||||
name: keyword,
|
||||
type: 1,
|
||||
store_id: userStore.userInfo?.store_id || 2,
|
||||
});
|
||||
chineseSearchResults.value = Array.isArray(res) ? res : [];
|
||||
} catch {
|
||||
chineseSearchResults.value = [];
|
||||
}
|
||||
}, 300);
|
||||
|
||||
function selectNewDrug(drugId: number) {
|
||||
/**
|
||||
* 中药气泡:选中新增行
|
||||
* @param selectedItem 商品检索完整行
|
||||
*/
|
||||
function selectNewDrug(selectedItem: any) {
|
||||
const drugId = Number(selectedItem?.drug?.id || selectedItem?.drug_id || 0);
|
||||
if (!drugId) return;
|
||||
const exists = drugList.value.some(
|
||||
(item) => item.id === drugId || item.drug_id === drugId,
|
||||
);
|
||||
if (exists) {
|
||||
message.warning('该药品已在列表中');
|
||||
newDrugInfo.value.id = undefined;
|
||||
newDrugInfo.value.name = '';
|
||||
return;
|
||||
}
|
||||
const selectedItem = chineseSearchResults.value.find(
|
||||
(item) => item.drug?.id === drugId || item.drug_id === drugId,
|
||||
);
|
||||
if (selectedItem) {
|
||||
newDrugInfo.value.name = selectedItem.drug?.drug_name || '';
|
||||
newDrugInfo.value.price = selectedItem.price || 0;
|
||||
newDrugInfo.value.way_id = 0;
|
||||
newDrugInfo.value.unit =
|
||||
selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
newDrugInfo.value.unit_id =
|
||||
selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector('.new-chinese-number input') as HTMLInputElement;
|
||||
numberInput?.focus();
|
||||
});
|
||||
}
|
||||
newDrugInfo.value.id = drugId;
|
||||
newDrugInfo.value.name = selectedItem.drug?.drug_name || '';
|
||||
newDrugInfo.value.price = selectedItem.price || 0;
|
||||
newDrugInfo.value.way_id = 0;
|
||||
newDrugInfo.value.unit =
|
||||
selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
newDrugInfo.value.unit_id =
|
||||
selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector(
|
||||
'.new-chinese-number input',
|
||||
) as HTMLInputElement;
|
||||
numberInput?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
function addChineseDrug() {
|
||||
@@ -216,11 +200,12 @@ function addChineseDrug() {
|
||||
unit: newDrugInfo.value.unit || null,
|
||||
});
|
||||
newDrugInfo.value = {};
|
||||
chineseSearchResults.value = [];
|
||||
syncToParent();
|
||||
message.success('已添加药品');
|
||||
nextTick(() => {
|
||||
const nameInput = document.querySelector('.new-chinese-name input') as HTMLInputElement;
|
||||
const nameInput = document.querySelector(
|
||||
'.new-chinese-name input',
|
||||
) as HTMLInputElement;
|
||||
nameInput?.focus();
|
||||
});
|
||||
}
|
||||
@@ -234,34 +219,36 @@ function handleChineseKeydown(event: KeyboardEvent, isNew: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
function changeChineseDrug(index: number, drugId: number) {
|
||||
/**
|
||||
* 中药气泡:替换已有行
|
||||
* @param index 行下标
|
||||
* @param selectedItem 商品检索完整行
|
||||
*/
|
||||
function changeChineseDrug(index: number, selectedItem: any) {
|
||||
const drugId = Number(selectedItem?.drug?.id || selectedItem?.drug_id || 0);
|
||||
if (!drugId) return;
|
||||
const exists = drugList.value.some(
|
||||
(item, i) => i !== index && (item.id === drugId || item.drug_id === drugId),
|
||||
);
|
||||
if (exists) {
|
||||
message.warning('该药品已在列表中');
|
||||
const drug = drugList.value[index];
|
||||
if (drug) drug.id = drug.drug_id;
|
||||
return;
|
||||
}
|
||||
const selectedItem = chineseSearchResults.value.find(
|
||||
(item) => item.drug?.id === drugId || item.drug_id === drugId,
|
||||
);
|
||||
if (selectedItem) {
|
||||
const drug = drugList.value[index];
|
||||
if (drug) {
|
||||
drug.id = drugId;
|
||||
drug.drug_id = drugId;
|
||||
drug.drug_name = selectedItem.drug?.drug_name || '';
|
||||
drug.price = selectedItem.price || 0;
|
||||
drug.unit = selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
drug.unit_id = selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
syncToParent();
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector(`.chinese-number-${index} input`) as HTMLInputElement;
|
||||
numberInput?.focus();
|
||||
});
|
||||
}
|
||||
const drug = drugList.value[index];
|
||||
if (drug) {
|
||||
drug.id = drugId;
|
||||
drug.drug_id = drugId;
|
||||
drug.drug_name = selectedItem.drug?.drug_name || '';
|
||||
drug.price = selectedItem.price || 0;
|
||||
drug.unit = selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
drug.unit_id = selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
syncToParent();
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector(
|
||||
`.chinese-number-${index} input`,
|
||||
) as HTMLInputElement;
|
||||
numberInput?.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,27 +410,15 @@ defineExpose({
|
||||
<span>{{ getWayName(drug.way_id) }}</span>
|
||||
</div>
|
||||
<div v-else class="chinese-drug-form">
|
||||
<Select
|
||||
v-model:value="drug.id"
|
||||
show-search
|
||||
:filter-option="false"
|
||||
<ChineseDrugNameBubble
|
||||
:display-name="drug.drug_name"
|
||||
:store-id="userStore.userInfo?.store_id || 0"
|
||||
:type="1"
|
||||
compact
|
||||
input-class="chinese-drug-name"
|
||||
placeholder="药名"
|
||||
class="chinese-drug-name"
|
||||
@search="searchChineseDrugs"
|
||||
@change="(val) => changeChineseDrug(index, val as number)"
|
||||
>
|
||||
<SelectOption v-if="chineseSearchResults.length === 0" :value="drug.id">
|
||||
{{ drug.drug_name }}
|
||||
</SelectOption>
|
||||
<SelectOption
|
||||
v-for="item in chineseSearchResults"
|
||||
v-else
|
||||
:key="item.drug?.id || item.id"
|
||||
:value="item.drug?.id || item.id"
|
||||
>
|
||||
{{ item.drug?.drug_name || item.drug_name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
@select="(item) => changeChineseDrug(index, item)"
|
||||
/>
|
||||
<span class="chinese-drug-comma">,</span>
|
||||
<InputNumber
|
||||
v-model:value="drug.number"
|
||||
@@ -523,23 +498,15 @@ defineExpose({
|
||||
<div class="chinese-drug-content">
|
||||
<span class="chinese-drug-index">{{ drugList.length + 1 }}、</span>
|
||||
<div class="chinese-drug-form">
|
||||
<Select
|
||||
v-model:value="newDrugInfo.id"
|
||||
show-search
|
||||
:filter-option="false"
|
||||
<ChineseDrugNameBubble
|
||||
:display-name="newDrugInfo.name || ''"
|
||||
:store-id="userStore.userInfo?.store_id || 0"
|
||||
:type="1"
|
||||
compact
|
||||
input-class="chinese-drug-name new-chinese-name"
|
||||
placeholder="药名"
|
||||
class="chinese-drug-name new-chinese-name"
|
||||
@search="searchChineseDrugs"
|
||||
@change="selectNewDrug"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in chineseSearchResults"
|
||||
:key="item.drug?.id || item.id"
|
||||
:value="item.drug?.id || item.id"
|
||||
>
|
||||
{{ item.drug?.drug_name || item.drug_name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
@select="selectNewDrug"
|
||||
/>
|
||||
<span class="chinese-drug-comma">,</span>
|
||||
<InputNumber
|
||||
v-model:value="newDrugInfo.number"
|
||||
@@ -607,15 +574,18 @@ defineExpose({
|
||||
|
||||
.chinese-drug-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding-left: 28px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chinese-drug-name {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
/* 药名气泡锁宽,与克数/用法保持同一行 */
|
||||
.chinese-drug-form :deep(.chinese-drug-name-bubble) {
|
||||
width: 100px !important;
|
||||
min-width: 88px !important;
|
||||
max-width: 110px !important;
|
||||
flex: 0 0 100px !important;
|
||||
}
|
||||
|
||||
.chinese-drug-readonly {
|
||||
|
||||
@@ -100,7 +100,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal title="编辑药方" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
|
||||
<Modal title="编辑药方" class="w-[80%]">
|
||||
<div v-if="prescriptionType === 'chinese'">
|
||||
<ChineseDrugEditor
|
||||
ref="chineseDrugEditorRef"
|
||||
|
||||
@@ -67,7 +67,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="modalTitle" class="w-[420px]">
|
||||
<Modal :title="modalTitle" class="w-[80%]">
|
||||
<div class="free-shipping-dose-modal">
|
||||
<p v-if="spName" class="sp-name">特色方:{{ spName }}</p>
|
||||
<div class="form-row">
|
||||
|
||||
@@ -178,7 +178,7 @@ function bindPrescriptionTypeChange() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="`${isUpdate ? '编辑' : '新增'}特色方`" class="w-[80%] md:w-[50%] lg:w-[30%] h-[60%]">
|
||||
<Modal :title="`${isUpdate ? '编辑' : '新增'}特色方`" class="w-[80%]">
|
||||
<Form />
|
||||
<div v-if="prescriptionType === 'chinese'" class="mt-4 border-t pt-4">
|
||||
<ChineseDrugEditor
|
||||
|
||||
@@ -91,7 +91,9 @@ const downloadQRCode = async (name: string, isQr = false) => {
|
||||
{{ data.qr_code.store.name }}
|
||||
</div>
|
||||
<div class="qrAddress">
|
||||
<span class="addressTitle">诊所地址:</span>{{ data.qr_code.store.position }}
|
||||
<span class="addressTitle">{{
|
||||
Number(data?.qr_code?.store?.type) === 1 ? '药店地址:' : '诊所地址:'
|
||||
}}</span>{{ data.qr_code.store.position }}
|
||||
</div>
|
||||
<Image :preview="false" :src="data.qr_code.qr_code" class="mt-3" height="30" width="30" />
|
||||
<div class="qrAddress">
|
||||
|
||||
@@ -484,6 +484,21 @@ export async function aiMatchPrescriptionDrugsApi(data: {
|
||||
return requestClient.post<any>(`${prefix}ai-match-prescription-drugs`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 出方:改选对照药品并写回 result_json(标记 is_modified)
|
||||
*/
|
||||
export async function aiSavePrescriptionDrugSelectionApi(data: {
|
||||
generation_id: number;
|
||||
item_index: number;
|
||||
selected_drug_id: number;
|
||||
selected_drug_name?: string;
|
||||
}) {
|
||||
return requestClient.post<any>(
|
||||
`${prefix}ai-save-prescription-drug-selection`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
/** 金方列表(VIP:golden_formula) */
|
||||
export async function goldenFormulaListApi(data: {
|
||||
keyword?: string;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
aiGeneratePrescriptionApi,
|
||||
aiListGenerationsApi,
|
||||
aiMatchPrescriptionDrugsApi,
|
||||
aiSavePrescriptionDrugSelectionApi,
|
||||
getProcessRuleList,
|
||||
} from '../api';
|
||||
import AiDisclaimerBanner from '#/views/doctor/components/AiDisclaimerBanner.vue';
|
||||
@@ -509,6 +510,46 @@ async function onSelectHistory(row: any) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 医生点选对照候选药:本地更新 + 同步写回 aijson(result_json)
|
||||
* @param mi matchedList 下标
|
||||
* @param candidate 候选药品
|
||||
*/
|
||||
async function onPickCandidate(mi: number, candidate: any) {
|
||||
const row = matchedList.value[mi];
|
||||
if (!row || !candidate) return;
|
||||
const drugId = Number(candidate.drug_id || 0);
|
||||
if (!drugId) return;
|
||||
if (Number(row.selected_drug_id) === drugId) return;
|
||||
const prevId = Number(row.selected_drug_id || 0);
|
||||
const prevModified = Number(row.is_modified || 0);
|
||||
row.selected_drug_id = drugId;
|
||||
row.is_modified = 1;
|
||||
const generationId = Number(activeId.value || 0);
|
||||
const itemIndex = Number(
|
||||
row.item_index !== undefined && row.item_index !== null
|
||||
? row.item_index
|
||||
: mi,
|
||||
);
|
||||
if (generationId <= 0) {
|
||||
message.warning('请先生成或选择历史处方后再改选');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await aiSavePrescriptionDrugSelectionApi({
|
||||
generation_id: generationId,
|
||||
item_index: itemIndex,
|
||||
selected_drug_id: drugId,
|
||||
selected_drug_name: String(candidate.drug_name || ''),
|
||||
});
|
||||
} catch (e: any) {
|
||||
// 回滚本地,避免 UI 与库不一致
|
||||
row.selected_drug_id = prevId;
|
||||
row.is_modified = prevModified;
|
||||
message.error(e?.message || e?.msg || '改选保存失败');
|
||||
}
|
||||
}
|
||||
|
||||
function onConfirmImport() {
|
||||
const drugs: any[] = [];
|
||||
for (const m of matchedList.value) {
|
||||
@@ -670,7 +711,16 @@ defineExpose({
|
||||
>
|
||||
<div class="flex items-start gap-1">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-xs font-medium">{{ m.ai_name }}</div>
|
||||
<div class="truncate text-xs font-medium">
|
||||
{{ m.ai_name }}
|
||||
<Tag
|
||||
v-if="Number(m.is_modified) === 1"
|
||||
color="orange"
|
||||
class="!m-0 !ml-1 !px-1 !text-[10px] !leading-4"
|
||||
>
|
||||
已改
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="text-[11px] text-muted-foreground">
|
||||
{{ m.dose }}{{ m.unit }}{{ m.usage ? `·${m.usage}` : '' }}
|
||||
</div>
|
||||
@@ -687,7 +737,7 @@ defineExpose({
|
||||
? 'success'
|
||||
: undefined
|
||||
"
|
||||
@click="m.selected_drug_id = Number(c.drug_id)"
|
||||
@click="onPickCandidate(mi, c)"
|
||||
>
|
||||
{{ c.drug_name }}
|
||||
</Tag>
|
||||
@@ -701,7 +751,16 @@ defineExpose({
|
||||
class="rounded-lg border border-border bg-card p-2.5"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="min-w-0 flex-1 truncate font-medium">{{ m.ai_name }}</span>
|
||||
<span class="min-w-0 flex-1 truncate font-medium">
|
||||
{{ m.ai_name }}
|
||||
<Tag
|
||||
v-if="Number(m.is_modified) === 1"
|
||||
color="orange"
|
||||
class="!m-0 !ml-1 !text-[10px]"
|
||||
>
|
||||
已改
|
||||
</Tag>
|
||||
</span>
|
||||
<span class="text-xs text-muted-foreground">
|
||||
{{ m.dose }}{{ m.unit }}{{ m.usage ? `·${m.usage}` : '' }}
|
||||
</span>
|
||||
@@ -717,7 +776,7 @@ defineExpose({
|
||||
? 'success'
|
||||
: undefined
|
||||
"
|
||||
@click="m.selected_drug_id = Number(c.drug_id)"
|
||||
@click="onPickCandidate(mi, c)"
|
||||
>
|
||||
{{ c.drug_name }} ¥{{ Number(c.price || 0).toFixed(2) }}
|
||||
</Tag>
|
||||
|
||||
@@ -97,6 +97,8 @@ import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
|
||||
import { detailDoctorOrderToText } from '#/utils/prescriptionDoctorOrder';
|
||||
// 药品搜索选择组件
|
||||
import { DrugSearchSelect } from '#/components/drug-search-select';
|
||||
// 中药药名气泡(对齐诊断/医嘱浮层交互)
|
||||
import { ChineseDrugNameBubble } from '#/components/chinese-drug-name-bubble';
|
||||
// 保存常用方API
|
||||
import {
|
||||
saveChineseCommonPrescriptionApi,
|
||||
@@ -2599,28 +2601,114 @@ const getDrugListByWesternModal = debounce(async (searchText = '') => {
|
||||
|
||||
// getDrugListByWesternModal();
|
||||
|
||||
/** 新行中药气泡选中的完整商品(添加时用,避免再依赖 drugList) */
|
||||
const pendingNewChineseProduct = ref<any>(null);
|
||||
|
||||
/**
|
||||
* 选择药品
|
||||
* 中药气泡:选中新增行药名
|
||||
* @param item 商品检索完整行
|
||||
*/
|
||||
function onSelectNewChineseDrug(item: any) {
|
||||
const drugId = Number(item?.drug_id || item?.drug?.id || 0);
|
||||
if (!drugId) return;
|
||||
const check = currentDrugs.value.find((v) => Number(v.id) === drugId);
|
||||
if (check) {
|
||||
newDrugInfo.value.id = '';
|
||||
newDrugInfo.value.name = '';
|
||||
pendingNewChineseProduct.value = null;
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
pendingNewChineseProduct.value = item;
|
||||
newDrugInfo.value.id = drugId;
|
||||
newDrugInfo.value.price = item.price;
|
||||
newDrugInfo.value.name = item.drug?.drug_name || '';
|
||||
newDrugInfo.value.unit =
|
||||
item.drug?.unit ||
|
||||
drugUnit.value.find((u) => u.id === item.drug?.unit_id);
|
||||
newDrugInfo.value.unit_id = item.drug?.unit_id;
|
||||
setTimeout(() => {
|
||||
const newDrugNumberInput = document.querySelector(
|
||||
'.new-number-input input',
|
||||
);
|
||||
if (newDrugNumberInput) {
|
||||
(newDrugNumberInput as HTMLElement).focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 中药气泡:替换已选行药名
|
||||
* @param index 行下标
|
||||
* @param item 商品检索完整行
|
||||
*/
|
||||
function onSelectOldChineseDrug(index: number, item: any) {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
const drugId = Number(item?.drug?.id || item?.drug_id || 0);
|
||||
if (!drugId) return;
|
||||
const check = currentDrugs.value.find(
|
||||
(v, i) => i !== index && Number(v.id) === drugId,
|
||||
);
|
||||
if (check) {
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
const data = item;
|
||||
const newProduct = {
|
||||
index_id: data.id,
|
||||
id: data.drug.id,
|
||||
drug_name: data.drug.drug_name,
|
||||
number: data.drug.number,
|
||||
use_num: drugTime.value.find((u) => u.id === data.drug.time_id),
|
||||
use_type: drugUseType.value.find((u) => u.id === data.drug.type_id),
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(u) => u.id === data.drug.frequency_id,
|
||||
),
|
||||
unit:
|
||||
data.drug?.unit ||
|
||||
drugUnit.value.find((u) => u.id === data.drug.unit_id),
|
||||
price: data.price,
|
||||
buy_price: data.buy_price,
|
||||
way_id: data.drug?.way_id,
|
||||
use_ways: drugUseWay.value.find((u) => u.id === data.drug.way_id),
|
||||
time_id: data.drug.time_id,
|
||||
type_id: data.drug.type_id,
|
||||
frequency_id: data.drug.frequency_id,
|
||||
unit_id: data.drug.unit_id,
|
||||
image: data.drug.image,
|
||||
instruction: data.drug.instruction,
|
||||
type: data.drug.type,
|
||||
};
|
||||
currentDrugs.value[index] = newProduct;
|
||||
updateLocalStorage();
|
||||
setTimeout(() => {
|
||||
const numberInput = document.querySelector(
|
||||
`.old-number-input-${index} input`,
|
||||
);
|
||||
if (numberInput) {
|
||||
(numberInput as HTMLElement).focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/** 兼容西药等仍走 Select 的旧逻辑 */
|
||||
function selectNewDrugInfo() {
|
||||
// 如果是新药品输入框,调用添加新药品方法
|
||||
const check = currentDrugs.value.find((v) => v.id === newDrugInfo.value.id);
|
||||
if (check) {
|
||||
newDrugInfo.value.id = '';
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
|
||||
if (!data) return;
|
||||
pendingNewChineseProduct.value = data;
|
||||
newDrugInfo.value.price = data.price;
|
||||
newDrugInfo.value.name = data.drug.drug_name;
|
||||
// 选药后立刻带上真实单位,供新行数量后缀展示(优先接口返回的 unit)
|
||||
newDrugInfo.value.unit =
|
||||
data.drug?.unit ||
|
||||
drugUnit.value.find((item) => item.id === data.drug?.unit_id);
|
||||
newDrugInfo.value.unit_id = data.drug?.unit_id;
|
||||
setTimeout(() => {
|
||||
// 获取新药品卡片中的数量输入框并聚焦
|
||||
const newDrugNumberInput = document.querySelector(
|
||||
'.new-number-input input',
|
||||
);
|
||||
@@ -2634,7 +2722,7 @@ const selectChineseIndex = ref(-1);
|
||||
const selectChineseId = ref(0);
|
||||
|
||||
/**
|
||||
* 选择药品
|
||||
* 选择药品(西药等 Select 仍用)
|
||||
* @param id
|
||||
*/
|
||||
function selectOldDrugInfo(id) {
|
||||
@@ -2648,59 +2736,36 @@ function selectOldDrugInfo(id) {
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
const oldDrugInfo = currentDrugs.value[selectChineseIndex.value];
|
||||
// 如果是新药品输入框,调用添加新药品方法
|
||||
const data = drugList.value.find((v) => v.drug_id === id);
|
||||
|
||||
// 创建新的商品对象(避免直接修改原始数据)
|
||||
if (!data) return;
|
||||
const newProduct = {
|
||||
// 索引ID(用于在列表中查找)
|
||||
index_id: data.id,
|
||||
// 药品ID
|
||||
id: data.drug.id,
|
||||
// 药品名称
|
||||
drug_name: data.drug.drug_name,
|
||||
// 药品数量
|
||||
number: data.drug.number,
|
||||
// 药品使用数量信息
|
||||
use_num: drugTime.value.find((item) => item.id === data.drug.time_id),
|
||||
// 药品使用类型信息
|
||||
use_type: drugUseType.value.find((item) => item.id === data.drug.type_id),
|
||||
// 药品使用频率信息
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(item) => item.id === data.drug.frequency_id,
|
||||
),
|
||||
// 药品单位:优先用选药接口返回的 unit,再回退本地字典
|
||||
unit:
|
||||
data.drug?.unit ||
|
||||
drugUnit.value.find((item) => item.id === data.drug.unit_id),
|
||||
// 药品价格
|
||||
price: data.price,
|
||||
buy_price: data.buy_price,
|
||||
// 药品使用方式ID
|
||||
way_id: data.drug?.way_id,
|
||||
// 药品使用方式信息
|
||||
use_ways: drugUseWay.value.find((item) => item.id === data.drug.way_id),
|
||||
// 药品使用时间ID
|
||||
time_id: data.drug.time_id,
|
||||
// 药品类型ID
|
||||
type_id: data.drug.type_id,
|
||||
// 药品使用频率ID
|
||||
frequency_id: data.drug.frequency_id,
|
||||
// 药品单位ID
|
||||
unit_id: data.drug.unit_id,
|
||||
// 药品图片
|
||||
image: data.drug.image,
|
||||
// 药品说明书
|
||||
instruction: data.drug.instruction,
|
||||
// 药品类型
|
||||
type: data.drug.type,
|
||||
};
|
||||
currentDrugs.value[selectChineseIndex.value] = newProduct;
|
||||
updateLocalStorage();
|
||||
// const data = drugList.value.find((v) => v.drug_id === id);
|
||||
setTimeout(() => {
|
||||
// 获取新药品卡片中的克数输入框并聚焦
|
||||
const newDrugNumberInput = document.querySelector(
|
||||
`.old-number-input-${selectChineseIndex.value} input`,
|
||||
);
|
||||
@@ -2732,7 +2797,6 @@ function searchOption(inputValue) {
|
||||
*/
|
||||
function addDrugByChinese() {
|
||||
if (guardSpecialPrescriptionCartEdit()) return;
|
||||
// 如果是新药品输入框,调用添加新药品方法
|
||||
const check = currentDrugs.value.find(
|
||||
(v) => v.id === newDrugInfo.value.id,
|
||||
);
|
||||
@@ -2740,9 +2804,10 @@ function addDrugByChinese() {
|
||||
message.error('该药品已经在处方中了!');
|
||||
return;
|
||||
}
|
||||
const data = drugList.value.find(
|
||||
(v) => v.drug_id === newDrugInfo.value.id,
|
||||
);
|
||||
// 优先用气泡选中缓存,兼容西药 Select 仍写在 drugList 的场景
|
||||
const data =
|
||||
pendingNewChineseProduct.value ||
|
||||
drugList.value.find((v) => v.drug_id === newDrugInfo.value.id);
|
||||
if (data === null || data === undefined) {
|
||||
message.error('请选择药品');
|
||||
return;
|
||||
@@ -2751,6 +2816,7 @@ function addDrugByChinese() {
|
||||
data.drug.way_id = newDrugInfo.value.way_id;
|
||||
addProducts(data);
|
||||
newDrugInfo.value = {};
|
||||
pendingNewChineseProduct.value = null;
|
||||
}
|
||||
/**
|
||||
* 添加操作
|
||||
@@ -3430,32 +3496,17 @@ function onStoreSelectOpenChange(open: boolean) {
|
||||
<div>
|
||||
<span class="card-index">{{ index + 1 }}、</span>
|
||||
<p>
|
||||
<Select
|
||||
v-model:value="drug.id"
|
||||
<ChineseDrugNameBubble
|
||||
:display-name="drug.drug_name"
|
||||
:disabled="isSpecialPrescriptionCartLocked"
|
||||
:filter-option="false"
|
||||
class="old-select-drug-name"
|
||||
style="min-width: 100px"
|
||||
:store-id="myStoreId"
|
||||
:register-id="getRegisterId()"
|
||||
:type="1"
|
||||
compact
|
||||
input-class="old-select-drug-name"
|
||||
placeholder="药名"
|
||||
show-search
|
||||
@change="selectOldDrugInfo"
|
||||
@dropdown-visible-change="
|
||||
setSelectChineseIndex(index, drug.id)
|
||||
"
|
||||
@search="searchOption"
|
||||
>
|
||||
<SelectOption v-if="drugList.length === 0" :value="drug.id">
|
||||
{{ drug.drug_name }}
|
||||
</SelectOption>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugList"
|
||||
v-else
|
||||
:key="index"
|
||||
:value="value.drug.id"
|
||||
>
|
||||
{{ value.drug.drug_name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
@select="(item) => onSelectOldChineseDrug(index, item)"
|
||||
/>
|
||||
,
|
||||
<InputNumber
|
||||
v-model:value="drug.number"
|
||||
@@ -3523,24 +3574,16 @@ function onStoreSelectOpenChange(open: boolean) {
|
||||
<div>
|
||||
<span class="card-index">{{ currentDrugs.length + 1 }}、</span>
|
||||
<div>
|
||||
<Select
|
||||
v-model:value="newDrugInfo.id"
|
||||
:filter-option="false"
|
||||
class="new-select-drug-name w-1/5"
|
||||
<ChineseDrugNameBubble
|
||||
:display-name="newDrugInfo.name || ''"
|
||||
:store-id="myStoreId"
|
||||
:register-id="getRegisterId()"
|
||||
:type="1"
|
||||
compact
|
||||
input-class="new-select-drug-name"
|
||||
placeholder="药名"
|
||||
show-search
|
||||
style="min-width: 100px"
|
||||
@change="selectNewDrugInfo"
|
||||
@search="searchOption"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="(value, index) in drugList"
|
||||
:key="index"
|
||||
:value="value.drug_id"
|
||||
>
|
||||
{{ value.drug.drug_name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
@select="onSelectNewChineseDrug"
|
||||
/>
|
||||
,
|
||||
<InputNumber
|
||||
v-model:value="newDrugInfo.number"
|
||||
@@ -4154,6 +4197,14 @@ function onStoreSelectOpenChange(open: boolean) {
|
||||
left: 10px;
|
||||
top: 0;
|
||||
}
|
||||
/* 中药卡片:药名气泡锁宽,与克数/用法保持同一行 */
|
||||
:deep(.chinese-drug-name-bubble) {
|
||||
width: 100px !important;
|
||||
min-width: 88px !important;
|
||||
max-width: 110px !important;
|
||||
flex: 0 0 100px !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.card-price {
|
||||
position: absolute;
|
||||
|
||||
@@ -40,8 +40,8 @@ import {
|
||||
SelectOption,
|
||||
Spin,
|
||||
} from 'ant-design-vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import { ChineseDrugNameBubble } from '#/components/chinese-drug-name-bubble';
|
||||
import { DrugSearchSelect } from '#/components/drug-search-select';
|
||||
import {
|
||||
saveWestCommonPrescriptionApi,
|
||||
@@ -50,7 +50,6 @@ import {
|
||||
} from '#/views/doctor/settings/api';
|
||||
import {
|
||||
getDrugUseList,
|
||||
getProductListDoctorReception,
|
||||
getProcessRuleList,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
import {
|
||||
@@ -173,10 +172,6 @@ const processRuleNoteList = ref<any[]>([]);
|
||||
*/
|
||||
let onSaveCallback: (() => void) | null = null;
|
||||
|
||||
/**
|
||||
* 中药搜索关键词列表(每个药品对应一个搜索结果)
|
||||
*/
|
||||
const chineseSearchResults = ref<any[]>([]);
|
||||
|
||||
/**
|
||||
* 新药品输入数据
|
||||
@@ -364,75 +359,38 @@ function handleTypeChange() {
|
||||
// 切换类型时清空药品列表
|
||||
drugList.value = [];
|
||||
newDrugInfo.value = {};
|
||||
chineseSearchResults.value = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索中药(防抖)
|
||||
* @param keyword 搜索关键词
|
||||
* 中药气泡:选中新增行
|
||||
* @param selectedItem 商品检索完整行
|
||||
*/
|
||||
const searchChineseDrugs = debounce(async (keyword: string) => {
|
||||
if (!keyword || keyword.length < 1) {
|
||||
chineseSearchResults.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getProductListDoctorReception({
|
||||
name: keyword,
|
||||
type: 1, // 中药类型
|
||||
store_id: userStore.userInfo?.store_id || 2,
|
||||
});
|
||||
|
||||
if (Array.isArray(res)) {
|
||||
chineseSearchResults.value = res;
|
||||
} else {
|
||||
chineseSearchResults.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索中药失败:', error);
|
||||
chineseSearchResults.value = [];
|
||||
}
|
||||
}, 300);
|
||||
|
||||
/**
|
||||
* 选择新药品
|
||||
* @param drugId 药品ID(drug.id)
|
||||
*/
|
||||
function selectNewDrug(drugId: number) {
|
||||
// 检查是否已存在
|
||||
function selectNewDrug(selectedItem: any) {
|
||||
const drugId = Number(selectedItem?.drug?.id || selectedItem?.drug_id || 0);
|
||||
if (!drugId) return;
|
||||
const exists = drugList.value.some(
|
||||
(item) => item.id === drugId || item.drug_id === drugId,
|
||||
);
|
||||
|
||||
if (exists) {
|
||||
message.warning('该药品已在列表中');
|
||||
newDrugInfo.value.id = undefined;
|
||||
newDrugInfo.value.name = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到选中的药品信息
|
||||
const selectedItem = chineseSearchResults.value.find(
|
||||
(item) => item.drug?.id === drugId || item.drug_id === drugId,
|
||||
);
|
||||
|
||||
if (selectedItem) {
|
||||
newDrugInfo.value.name = selectedItem.drug?.drug_name || '';
|
||||
newDrugInfo.value.price = selectedItem.price || 0;
|
||||
newDrugInfo.value.way_id = 0;
|
||||
newDrugInfo.value.unit =
|
||||
selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
newDrugInfo.value.unit_id =
|
||||
selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
|
||||
// 聚焦到数量输入框
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector('.new-chinese-number input') as HTMLInputElement;
|
||||
if (numberInput) {
|
||||
numberInput.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
newDrugInfo.value.id = drugId;
|
||||
newDrugInfo.value.name = selectedItem.drug?.drug_name || '';
|
||||
newDrugInfo.value.price = selectedItem.price || 0;
|
||||
newDrugInfo.value.way_id = 0;
|
||||
newDrugInfo.value.unit =
|
||||
selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
newDrugInfo.value.unit_id =
|
||||
selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector(
|
||||
'.new-chinese-number input',
|
||||
) as HTMLInputElement;
|
||||
numberInput?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -474,7 +432,6 @@ function addChineseDrug() {
|
||||
|
||||
// 清空并准备下一次输入
|
||||
newDrugInfo.value = {};
|
||||
chineseSearchResults.value = [];
|
||||
|
||||
message.success('已添加药品');
|
||||
|
||||
@@ -488,50 +445,35 @@ function addChineseDrug() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改已有中药(切换药品)
|
||||
* 中药气泡:替换已有行
|
||||
* @param index 药品索引
|
||||
* @param drugId 新药品ID
|
||||
* @param selectedItem 商品检索完整行
|
||||
*/
|
||||
function changeChineseDrug(index: number, drugId: number) {
|
||||
// 检查是否已存在(排除当前正在修改的)
|
||||
function changeChineseDrug(index: number, selectedItem: any) {
|
||||
const drugId = Number(selectedItem?.drug?.id || selectedItem?.drug_id || 0);
|
||||
if (!drugId) return;
|
||||
const exists = drugList.value.some(
|
||||
(item, i) => i !== index && (item.id === drugId || item.drug_id === drugId),
|
||||
);
|
||||
|
||||
if (exists) {
|
||||
message.warning('该药品已在列表中');
|
||||
// 恢复原来的值
|
||||
const drug = drugList.value[index];
|
||||
if (drug) {
|
||||
drug.id = drug.drug_id;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到选中的药品信息
|
||||
const selectedItem = chineseSearchResults.value.find(
|
||||
(item) => item.drug?.id === drugId || item.drug_id === drugId,
|
||||
);
|
||||
|
||||
if (selectedItem) {
|
||||
const drug = drugList.value[index];
|
||||
if (drug) {
|
||||
drug.id = drugId;
|
||||
drug.drug_id = drugId;
|
||||
drug.drug_name = selectedItem.drug?.drug_name || '';
|
||||
drug.price = selectedItem.price || 0;
|
||||
drug.unit = selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
drug.unit_id = selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
}
|
||||
|
||||
// 聚焦到数量输入框
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector(`.chinese-number-${index} input`) as HTMLInputElement;
|
||||
if (numberInput) {
|
||||
numberInput.focus();
|
||||
}
|
||||
});
|
||||
const drug = drugList.value[index];
|
||||
if (drug) {
|
||||
drug.id = drugId;
|
||||
drug.drug_id = drugId;
|
||||
drug.drug_name = selectedItem.drug?.drug_name || '';
|
||||
drug.price = selectedItem.price || 0;
|
||||
drug.unit = selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
drug.unit_id = selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
}
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector(
|
||||
`.chinese-number-${index} input`,
|
||||
) as HTMLInputElement;
|
||||
numberInput?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -779,7 +721,7 @@ async function handleSave() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[900px]">
|
||||
<Modal class="w-[50%]">
|
||||
<Spin :spinning="isSaving">
|
||||
<div class="p-4">
|
||||
<!-- 类型选择 -->
|
||||
@@ -1100,30 +1042,15 @@ async function handleSave() {
|
||||
<div class="chinese-drug-content">
|
||||
<span class="chinese-drug-index">{{ index + 1 }}、</span>
|
||||
<div class="chinese-drug-form">
|
||||
<Select
|
||||
v-model:value="drug.id"
|
||||
show-search
|
||||
:filter-option="false"
|
||||
<ChineseDrugNameBubble
|
||||
:display-name="drug.drug_name"
|
||||
:store-id="userStore.userInfo?.store_id || 0"
|
||||
:type="1"
|
||||
compact
|
||||
input-class="chinese-drug-name"
|
||||
placeholder="药名"
|
||||
class="chinese-drug-name"
|
||||
@search="searchChineseDrugs"
|
||||
@change="(val) => changeChineseDrug(index, val)"
|
||||
>
|
||||
<SelectOption
|
||||
v-if="chineseSearchResults.length === 0"
|
||||
:value="drug.id"
|
||||
>
|
||||
{{ drug.drug_name }}
|
||||
</SelectOption>
|
||||
<SelectOption
|
||||
v-for="item in chineseSearchResults"
|
||||
v-else
|
||||
:key="item.drug?.id || item.id"
|
||||
:value="item.drug?.id || item.id"
|
||||
>
|
||||
{{ item.drug?.drug_name || item.drug_name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
@select="(item) => changeChineseDrug(index, item)"
|
||||
/>
|
||||
<span class="chinese-drug-comma">,</span>
|
||||
<InputNumber
|
||||
v-model:value="drug.number"
|
||||
@@ -1169,23 +1096,15 @@ async function handleSave() {
|
||||
<div class="chinese-drug-content">
|
||||
<span class="chinese-drug-index">{{ drugList.length + 1 }}、</span>
|
||||
<div class="chinese-drug-form">
|
||||
<Select
|
||||
v-model:value="newDrugInfo.id"
|
||||
show-search
|
||||
:filter-option="false"
|
||||
<ChineseDrugNameBubble
|
||||
:display-name="newDrugInfo.name || ''"
|
||||
:store-id="userStore.userInfo?.store_id || 0"
|
||||
:type="1"
|
||||
compact
|
||||
input-class="chinese-drug-name new-chinese-name"
|
||||
placeholder="药名"
|
||||
class="chinese-drug-name new-chinese-name"
|
||||
@search="searchChineseDrugs"
|
||||
@change="selectNewDrug"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in chineseSearchResults"
|
||||
:key="item.drug?.id || item.id"
|
||||
:value="item.drug?.id || item.id"
|
||||
>
|
||||
{{ item.drug?.drug_name || item.drug_name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
@select="selectNewDrug"
|
||||
/>
|
||||
<span class="chinese-drug-comma">,</span>
|
||||
<InputNumber
|
||||
v-model:value="newDrugInfo.number"
|
||||
@@ -1301,15 +1220,18 @@ async function handleSave() {
|
||||
|
||||
.chinese-drug-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding-left: 28px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chinese-drug-name {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
/* 药名气泡锁宽,与克数/用法保持同一行 */
|
||||
.chinese-drug-form :deep(.chinese-drug-name-bubble) {
|
||||
width: 100px !important;
|
||||
min-width: 88px !important;
|
||||
max-width: 110px !important;
|
||||
flex: 0 0 100px !important;
|
||||
}
|
||||
|
||||
.chinese-drug-comma {
|
||||
|
||||
@@ -39,8 +39,7 @@ import {
|
||||
Spin,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import { ChineseDrugNameBubble } from '#/components/chinese-drug-name-bubble';
|
||||
import { DrugSearchSelect } from '#/components/drug-search-select';
|
||||
import {
|
||||
updateWestCommonPrescriptionApi,
|
||||
@@ -49,7 +48,6 @@ import {
|
||||
} from '#/views/doctor/settings/api';
|
||||
import {
|
||||
getDrugUseList,
|
||||
getProductListDoctorReception,
|
||||
getProcessRuleList,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
import {
|
||||
@@ -173,10 +171,6 @@ const processRuleNoteList = ref<any[]>([]);
|
||||
*/
|
||||
let onSaveCallback: (() => void) | null = null;
|
||||
|
||||
/**
|
||||
* 中药搜索结果列表
|
||||
*/
|
||||
const chineseSearchResults = ref<any[]>([]);
|
||||
|
||||
/**
|
||||
* 新药品输入数据
|
||||
@@ -287,7 +281,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
loadDrugUseData();
|
||||
// 加载委托调剂选项(第一级:制剂)
|
||||
await loadProcessRuleData(0, 0);
|
||||
|
||||
|
||||
// 如果有已保存的委托调剂配置,需要级联加载(顺序执行)
|
||||
if (processRuleId.value) {
|
||||
await loadProcessRuleData(processRuleId.value, 0);
|
||||
@@ -410,72 +404,35 @@ function openDiagnosisModal() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索中药(防抖)
|
||||
* @param keyword 搜索关键词
|
||||
* 中药气泡:选中新增行
|
||||
* @param selectedItem 商品检索完整行
|
||||
*/
|
||||
const searchChineseDrugs = debounce(async (keyword: string) => {
|
||||
if (!keyword || keyword.length < 1) {
|
||||
chineseSearchResults.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await getProductListDoctorReception({
|
||||
name: keyword,
|
||||
type: 1, // 中药类型
|
||||
store_id: userStore.userInfo?.store_id || 2,
|
||||
});
|
||||
|
||||
if (Array.isArray(res)) {
|
||||
chineseSearchResults.value = res;
|
||||
} else {
|
||||
chineseSearchResults.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('搜索中药失败:', error);
|
||||
chineseSearchResults.value = [];
|
||||
}
|
||||
}, 300);
|
||||
|
||||
/**
|
||||
* 选择新药品
|
||||
* @param drugId 药品ID
|
||||
*/
|
||||
function selectNewDrug(drugId: number) {
|
||||
// 检查是否已存在
|
||||
function selectNewDrug(selectedItem: any) {
|
||||
const drugId = Number(selectedItem?.drug?.id || selectedItem?.drug_id || 0);
|
||||
if (!drugId) return;
|
||||
const exists = drugList.value.some(
|
||||
(item) => item.id === drugId || item.drug_id === drugId,
|
||||
);
|
||||
|
||||
if (exists) {
|
||||
message.warning('该药品已在列表中');
|
||||
newDrugInfo.value.id = undefined;
|
||||
newDrugInfo.value.name = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到选中的药品信息
|
||||
const selectedItem = chineseSearchResults.value.find(
|
||||
(item) => item.drug?.id === drugId || item.drug_id === drugId,
|
||||
);
|
||||
|
||||
if (selectedItem) {
|
||||
newDrugInfo.value.name = selectedItem.drug?.drug_name || '';
|
||||
newDrugInfo.value.price = selectedItem.price || 0;
|
||||
newDrugInfo.value.way_id = 0;
|
||||
// 选药后带上真实单位,供数量后缀展示
|
||||
newDrugInfo.value.unit =
|
||||
selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
newDrugInfo.value.unit_id =
|
||||
selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
|
||||
// 聚焦到数量输入框
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector('.edit-new-chinese-number input') as HTMLInputElement;
|
||||
if (numberInput) {
|
||||
numberInput.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
newDrugInfo.value.id = drugId;
|
||||
newDrugInfo.value.name = selectedItem.drug?.drug_name || '';
|
||||
newDrugInfo.value.price = selectedItem.price || 0;
|
||||
newDrugInfo.value.way_id = 0;
|
||||
newDrugInfo.value.unit =
|
||||
selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
newDrugInfo.value.unit_id =
|
||||
selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector(
|
||||
'.edit-new-chinese-number input',
|
||||
) as HTMLInputElement;
|
||||
numberInput?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -517,7 +474,6 @@ function addChineseDrug() {
|
||||
|
||||
// 清空并准备下一次输入
|
||||
newDrugInfo.value = {};
|
||||
chineseSearchResults.value = [];
|
||||
|
||||
message.success('已添加药品');
|
||||
|
||||
@@ -531,50 +487,35 @@ function addChineseDrug() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改已有中药(切换药品)
|
||||
* 中药气泡:替换已有行
|
||||
* @param index 药品索引
|
||||
* @param drugId 新药品ID
|
||||
* @param selectedItem 商品检索完整行
|
||||
*/
|
||||
function changeChineseDrug(index: number, drugId: number) {
|
||||
// 检查是否已存在(排除当前正在修改的)
|
||||
function changeChineseDrug(index: number, selectedItem: any) {
|
||||
const drugId = Number(selectedItem?.drug?.id || selectedItem?.drug_id || 0);
|
||||
if (!drugId) return;
|
||||
const exists = drugList.value.some(
|
||||
(item, i) => i !== index && (item.id === drugId || item.drug_id === drugId),
|
||||
);
|
||||
|
||||
if (exists) {
|
||||
message.warning('该药品已在列表中');
|
||||
// 恢复原来的值
|
||||
const drug = drugList.value[index];
|
||||
if (drug) {
|
||||
drug.id = drug.drug_id;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到选中的药品信息
|
||||
const selectedItem = chineseSearchResults.value.find(
|
||||
(item) => item.drug?.id === drugId || item.drug_id === drugId,
|
||||
);
|
||||
|
||||
if (selectedItem) {
|
||||
const drug = drugList.value[index];
|
||||
if (drug) {
|
||||
drug.id = drugId;
|
||||
drug.drug_id = drugId;
|
||||
drug.drug_name = selectedItem.drug?.drug_name || '';
|
||||
drug.price = selectedItem.price || 0;
|
||||
drug.unit = selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
drug.unit_id = selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
}
|
||||
|
||||
// 聚焦到数量输入框
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector(`.edit-chinese-number-${index} input`) as HTMLInputElement;
|
||||
if (numberInput) {
|
||||
numberInput.focus();
|
||||
}
|
||||
});
|
||||
const drug = drugList.value[index];
|
||||
if (drug) {
|
||||
drug.id = drugId;
|
||||
drug.drug_id = drugId;
|
||||
drug.drug_name = selectedItem.drug?.drug_name || '';
|
||||
drug.price = selectedItem.price || 0;
|
||||
drug.unit = selectedItem.drug?.unit || selectedItem.unit || null;
|
||||
drug.unit_id = selectedItem.drug?.unit_id || selectedItem.unit_id || 0;
|
||||
}
|
||||
nextTick(() => {
|
||||
const numberInput = document.querySelector(
|
||||
`.edit-chinese-number-${index} input`,
|
||||
) as HTMLInputElement;
|
||||
numberInput?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -807,7 +748,7 @@ async function handleSave() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[900px]">
|
||||
<Modal class="w-[50%]">
|
||||
<Spin :spinning="isSaving">
|
||||
<div class="p-4">
|
||||
<!-- 类型标签 -->
|
||||
@@ -1119,30 +1060,15 @@ async function handleSave() {
|
||||
<div class="chinese-drug-content">
|
||||
<span class="chinese-drug-index">{{ index + 1 }}、</span>
|
||||
<div class="chinese-drug-form">
|
||||
<Select
|
||||
v-model:value="drug.id"
|
||||
show-search
|
||||
:filter-option="false"
|
||||
<ChineseDrugNameBubble
|
||||
:display-name="drug.drug_name"
|
||||
:store-id="userStore.userInfo?.store_id || 0"
|
||||
:type="1"
|
||||
compact
|
||||
input-class="chinese-drug-name"
|
||||
placeholder="药名"
|
||||
class="chinese-drug-name"
|
||||
@search="searchChineseDrugs"
|
||||
@change="(val) => changeChineseDrug(index, val)"
|
||||
>
|
||||
<SelectOption
|
||||
v-if="chineseSearchResults.length === 0"
|
||||
:value="drug.id"
|
||||
>
|
||||
{{ drug.drug_name }}
|
||||
</SelectOption>
|
||||
<SelectOption
|
||||
v-for="item in chineseSearchResults"
|
||||
v-else
|
||||
:key="item.drug?.id || item.id"
|
||||
:value="item.drug?.id || item.id"
|
||||
>
|
||||
{{ item.drug?.drug_name || item.drug_name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
@select="(item) => changeChineseDrug(index, item)"
|
||||
/>
|
||||
<span class="chinese-drug-comma">,</span>
|
||||
<InputNumber
|
||||
v-model:value="drug.number"
|
||||
@@ -1188,23 +1114,15 @@ async function handleSave() {
|
||||
<div class="chinese-drug-content">
|
||||
<span class="chinese-drug-index">{{ drugList.length + 1 }}、</span>
|
||||
<div class="chinese-drug-form">
|
||||
<Select
|
||||
v-model:value="newDrugInfo.id"
|
||||
show-search
|
||||
:filter-option="false"
|
||||
<ChineseDrugNameBubble
|
||||
:display-name="newDrugInfo.name || ''"
|
||||
:store-id="userStore.userInfo?.store_id || 0"
|
||||
:type="1"
|
||||
compact
|
||||
input-class="chinese-drug-name edit-new-chinese-name"
|
||||
placeholder="药名"
|
||||
class="chinese-drug-name edit-new-chinese-name"
|
||||
@search="searchChineseDrugs"
|
||||
@change="selectNewDrug"
|
||||
>
|
||||
<SelectOption
|
||||
v-for="item in chineseSearchResults"
|
||||
:key="item.drug?.id || item.id"
|
||||
:value="item.drug?.id || item.id"
|
||||
>
|
||||
{{ item.drug?.drug_name || item.drug_name }}
|
||||
</SelectOption>
|
||||
</Select>
|
||||
@select="selectNewDrug"
|
||||
/>
|
||||
<span class="chinese-drug-comma">,</span>
|
||||
<InputNumber
|
||||
v-model:value="newDrugInfo.number"
|
||||
@@ -1290,15 +1208,18 @@ async function handleSave() {
|
||||
|
||||
.chinese-drug-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding-left: 28px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chinese-drug-name {
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
/* 药名气泡锁宽,与克数/用法保持同一行 */
|
||||
.chinese-drug-form :deep(.chinese-drug-name-bubble) {
|
||||
width: 100px !important;
|
||||
min-width: 88px !important;
|
||||
max-width: 110px !important;
|
||||
flex: 0 0 100px !important;
|
||||
}
|
||||
|
||||
.chinese-drug-comma {
|
||||
|
||||
@@ -110,12 +110,15 @@ function openStoreCard(row: Record<string, any>) {
|
||||
StoreCardModalApi.open();
|
||||
}
|
||||
|
||||
// 打开二维码预览弹窗
|
||||
const openQrCodeModal = (url, title, address) => {
|
||||
/**
|
||||
* 打开药店二维码预览(本页固定 type=1)
|
||||
*/
|
||||
const openQrCodeModal = (url, title, address, type = 1) => {
|
||||
QrCodePreviewApi.setData({
|
||||
values: url, // 二维码图片URL
|
||||
title, // 标题
|
||||
address, // 地址
|
||||
values: url,
|
||||
title,
|
||||
address,
|
||||
type,
|
||||
});
|
||||
QrCodePreviewApi.open();
|
||||
};
|
||||
@@ -534,6 +537,7 @@ const batchSyncDrugPrice = () => {
|
||||
row.qr_code,
|
||||
row.name,
|
||||
`${row.province.name}${row.city.name}${row.position}`,
|
||||
row.type ?? 1,
|
||||
)
|
||||
"
|
||||
/>
|
||||
|
||||
@@ -149,12 +149,15 @@ const openAddDoctorModal = (row: any) => {
|
||||
addDoctorModalApi.open();
|
||||
};
|
||||
|
||||
const openQrCodeModal = (url, title, address) => {
|
||||
/**
|
||||
* 打开诊所二维码预览(本页固定 type=0)
|
||||
*/
|
||||
const openQrCodeModal = (url, title, address, type = 0) => {
|
||||
QrCodePreviewApi.setData({
|
||||
// 表单值
|
||||
values: url,
|
||||
title,
|
||||
address,
|
||||
type,
|
||||
});
|
||||
QrCodePreviewApi.open();
|
||||
};
|
||||
@@ -618,6 +621,7 @@ const handleSwitchClinicType = (row: any) => {
|
||||
row.qr_code,
|
||||
row.name,
|
||||
`${row.province.name}${row.city.name}${row.position}`,
|
||||
row.type ?? 0,
|
||||
)
|
||||
"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user