feat: 药品别名
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
/**
|
||||
* 中药药名气泡选择器
|
||||
* - 对齐诊断/医嘱 EntryKeywordBubble:Input 聚焦弹出 Teleport 浮层
|
||||
* - 远程搜索药品(getProductListDoctorReception),拼音/名称高亮
|
||||
* - 远程搜索药品(getProductListDoctorReception),支持药名/别名/首拼/全拼/数字
|
||||
* - 列表展示拼音列;命中别名时展示匹配别名及其拼音
|
||||
* - 选中后回传完整商品行,供接诊/复诊/特色方/常用方复用现有落库逻辑
|
||||
*/
|
||||
import {
|
||||
@@ -65,11 +66,11 @@ const emit = defineEmits<{
|
||||
select: [item: ChineseDrugBubbleItem];
|
||||
}>();
|
||||
|
||||
const ROW_HEIGHT = 44;
|
||||
const ROW_HEIGHT = 48;
|
||||
const HINT_HEIGHT = 34;
|
||||
const HEAD_HEIGHT = 32;
|
||||
const PANEL_PAD = 8;
|
||||
const PREFERRED_PANEL = 320;
|
||||
const PREFERRED_PANEL = 360;
|
||||
|
||||
const containerRef = ref<HTMLElement | null>(null);
|
||||
const panelRef = ref<HTMLElement | null>(null);
|
||||
@@ -127,6 +128,31 @@ function drugNameOf(item: ChineseDrugBubbleItem): string {
|
||||
return String(item?.drug?.drug_name || item?.drug_name || '').trim();
|
||||
}
|
||||
|
||||
/** 关键词命中的别名(后端 matched_alias) */
|
||||
function matchedAliasOf(item: ChineseDrugBubbleItem): string {
|
||||
return String(
|
||||
item?.matched_alias || item?.drug?.matched_alias || '',
|
||||
).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼音展示:命中别名时用别名拼音,否则用药名首拼(全拼)
|
||||
*/
|
||||
function pinyinLabelOf(item: ChineseDrugBubbleItem): string {
|
||||
const aliasPy = String(
|
||||
item?.matched_alias_pinyin || item?.drug?.matched_alias_pinyin || '',
|
||||
).trim();
|
||||
if (matchedAliasOf(item) && aliasPy) return aliasPy;
|
||||
const display = String(item?.drug?.pinyin_display || '').trim();
|
||||
if (display) return display;
|
||||
const simple = String(item?.drug?.pinyin_simple || '').trim();
|
||||
const full = String(item?.drug?.pinyin_full || '').trim();
|
||||
if (simple && full && simple.toLowerCase() !== full.toLowerCase()) {
|
||||
return `${simple}(${full})`;
|
||||
}
|
||||
return simple || full || '-';
|
||||
}
|
||||
|
||||
function unitLabelOf(item: ChineseDrugBubbleItem): string {
|
||||
const unit = item?.drug?.unit || item?.unit;
|
||||
if (typeof unit === 'object' && unit?.name) return String(unit.name);
|
||||
@@ -155,7 +181,8 @@ function updatePanelPlacement() {
|
||||
const panelMax = Math.min(PREFERRED_PANEL, avail);
|
||||
listViewportH.value = Math.max(80, panelMax - HINT_HEIGHT - HEAD_HEIGHT);
|
||||
|
||||
const PANEL_MIN_WIDTH = 420;
|
||||
// 含拼音列后加宽,避免别名/全拼被裁切
|
||||
const PANEL_MIN_WIDTH = 560;
|
||||
const width = Math.min(
|
||||
Math.max(rect.width, PANEL_MIN_WIDTH),
|
||||
vw - PANEL_PAD * 2,
|
||||
@@ -228,9 +255,13 @@ async function fetchDrugs(kw: string) {
|
||||
(it) => drugNameOf(it),
|
||||
(it) =>
|
||||
[
|
||||
it.drug?.pinyin,
|
||||
it.drug?.pinyin_simple,
|
||||
it.drug?.pinyin_full,
|
||||
it.drug?.initials_of_pinyin,
|
||||
it.drug?.pinyin_display,
|
||||
it.drug?.drug_alias,
|
||||
it.matched_alias || it.drug?.matched_alias,
|
||||
it.matched_alias_pinyin || it.drug?.matched_alias_pinyin,
|
||||
it.drug?.search_digits,
|
||||
it.drug?.specification,
|
||||
]
|
||||
.map((v) => String(v || ''))
|
||||
@@ -440,7 +471,9 @@ defineExpose({
|
||||
v-if="!loading && options.length === 0"
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
:description="
|
||||
keyword.trim() ? '未找到匹配药材' : '请输入药名/拼音搜索'
|
||||
keyword.trim()
|
||||
? '未找到匹配药材'
|
||||
: '请输入药名/别名/拼音/数字搜索'
|
||||
"
|
||||
class="py-4"
|
||||
/>
|
||||
@@ -450,6 +483,7 @@ defineExpose({
|
||||
:class="{ 'has-score': !!keyword.trim() }"
|
||||
>
|
||||
<span class="col-name">药名</span>
|
||||
<span class="col-py">拼音</span>
|
||||
<span class="col-spec">规格/单位</span>
|
||||
<span class="col-price">价格</span>
|
||||
<span v-if="keyword.trim()" class="col-score">匹配度</span>
|
||||
@@ -480,13 +514,47 @@ defineExpose({
|
||||
@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}`"
|
||||
<span
|
||||
class="col-name"
|
||||
:title="
|
||||
matchedAliasOf(item)
|
||||
? `${drugNameOf(item)}(别名:${matchedAliasOf(item)})`
|
||||
: drugNameOf(item)
|
||||
"
|
||||
>
|
||||
<span class="col-name__main">
|
||||
<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
|
||||
v-if="matchedAliasOf(item)"
|
||||
class="col-name__alias"
|
||||
>
|
||||
<em v-if="p.hit" class="hit">{{ p.text }}</em>
|
||||
<template v-else>{{ p.text }}</template>
|
||||
别名:
|
||||
<template
|
||||
v-for="(p, pi) in partsOf(matchedAliasOf(item))"
|
||||
:key="`a_${pi}`"
|
||||
>
|
||||
<em v-if="p.hit" class="hit">{{ p.text }}</em>
|
||||
<template v-else>{{ p.text }}</template>
|
||||
</template>
|
||||
</span>
|
||||
</span>
|
||||
<span class="col-py" :title="pinyinLabelOf(item)">
|
||||
<template v-if="pinyinLabelOf(item) === '-'">-</template>
|
||||
<template v-else>
|
||||
<template
|
||||
v-for="(p, pi) in partsOf(pinyinLabelOf(item))"
|
||||
:key="`py_${pi}`"
|
||||
>
|
||||
<em v-if="p.hit" class="hit">{{ p.text }}</em>
|
||||
<template v-else>{{ p.text }}</template>
|
||||
</template>
|
||||
</template>
|
||||
</span>
|
||||
<span class="col-spec" :title="unitLabelOf(item)">
|
||||
@@ -589,7 +657,7 @@ defineExpose({
|
||||
.chinese-drug-name-bubble__head,
|
||||
.chinese-drug-name-bubble__row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 100px 72px;
|
||||
grid-template-columns: minmax(90px, 1.1fr) minmax(110px, 1.3fr) 90px 64px;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
@@ -598,7 +666,7 @@ defineExpose({
|
||||
}
|
||||
.chinese-drug-name-bubble__head.has-score,
|
||||
.chinese-drug-name-bubble__row.has-score {
|
||||
grid-template-columns: 1fr 90px 64px 48px;
|
||||
grid-template-columns: minmax(90px, 1.1fr) minmax(100px, 1.2fr) 80px 56px 48px;
|
||||
}
|
||||
.chinese-drug-name-bubble__head {
|
||||
flex-shrink: 0;
|
||||
@@ -617,13 +685,37 @@ defineExpose({
|
||||
color: hsl(var(--accent-foreground));
|
||||
}
|
||||
.col-name,
|
||||
.col-py,
|
||||
.col-spec {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.col-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
min-width: 0;
|
||||
font-weight: 500;
|
||||
white-space: normal;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.col-name__main {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.col-name__alias {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
.col-py {
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
.col-spec {
|
||||
font-size: 12px;
|
||||
@@ -641,7 +733,9 @@ defineExpose({
|
||||
color: #d4380d;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chinese-drug-name-bubble__row.is-active .col-spec {
|
||||
.chinese-drug-name-bubble__row.is-active .col-spec,
|
||||
.chinese-drug-name-bubble__row.is-active .col-py,
|
||||
.chinese-drug-name-bubble__row.is-active .col-name__alias {
|
||||
color: inherit;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ export interface WarehouseDrugItem {
|
||||
id: number;
|
||||
drug_name: string;
|
||||
pinyin_simple?: string;
|
||||
/** 关键词命中的别名(后端 matched_alias) */
|
||||
matched_alias?: string;
|
||||
/** 命中别名对应拼音展示 */
|
||||
matched_alias_pinyin?: string;
|
||||
specification?: string;
|
||||
image?: string;
|
||||
type?: number;
|
||||
@@ -212,8 +216,27 @@ watch(
|
||||
<div v-else class="warehouse-drug-item__img-placeholder">无图</div>
|
||||
</div>
|
||||
<div class="warehouse-drug-item__info">
|
||||
<div class="warehouse-drug-item__name">{{ item.drug_name }}</div>
|
||||
<div
|
||||
class="warehouse-drug-item__name"
|
||||
:title="
|
||||
item.matched_alias
|
||||
? `${item.drug_name}(别名:${item.matched_alias})`
|
||||
: item.drug_name
|
||||
"
|
||||
>
|
||||
{{ item.drug_name }}
|
||||
</div>
|
||||
<!-- 与开方气泡一致:命中别名时展示别名及拼音 -->
|
||||
<div v-if="item.matched_alias" class="warehouse-drug-item__alias">
|
||||
别名:{{ item.matched_alias }}
|
||||
<span v-if="item.matched_alias_pinyin">
|
||||
({{ item.matched_alias_pinyin }})
|
||||
</span>
|
||||
</div>
|
||||
<div class="warehouse-drug-item__meta">
|
||||
<span v-if="item.pinyin_simple && !item.matched_alias" class="warehouse-drug-item__py">
|
||||
拼音:{{ item.pinyin_simple }}
|
||||
</span>
|
||||
<span v-if="item.specification" class="warehouse-drug-item__spec">
|
||||
规格:{{ item.specification }}
|
||||
</span>
|
||||
@@ -321,7 +344,17 @@ watch(
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
&__alias {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
@@ -332,6 +365,10 @@ watch(
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&__py {
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
&__spec {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
const prefix = 'drug-alias/';
|
||||
|
||||
/**
|
||||
* 药品详情 + 别名(药名点击弹窗)
|
||||
*/
|
||||
export async function getDrugAliasDetail(drugId: number) {
|
||||
return requestClient.get<any>(`${prefix}detail`, {
|
||||
params: { drug_id: drugId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 整表覆盖保存别名(管理员/超管)
|
||||
*/
|
||||
export async function saveDrugAliases(
|
||||
drugId: number,
|
||||
aliases: string | string[],
|
||||
) {
|
||||
return requestClient.post<any>(`${prefix}save-aliases`, {
|
||||
drug_id: drugId,
|
||||
aliases,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增一条别名
|
||||
*/
|
||||
export async function addDrugAlias(drugId: number, alias: string) {
|
||||
return requestClient.post<any>(`${prefix}add-alias`, {
|
||||
drug_id: drugId,
|
||||
alias,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一条别名
|
||||
*/
|
||||
export async function deleteDrugAlias(drugId: number, aliasId: number) {
|
||||
return requestClient.post<any>(`${prefix}delete-alias`, {
|
||||
drug_id: drugId,
|
||||
alias_id: aliasId,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 药品详情弹窗:展示基础信息;管理员/超管可逐条添加/删除别名
|
||||
*/
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Descriptions,
|
||||
DescriptionsItem,
|
||||
Empty,
|
||||
Image,
|
||||
Input,
|
||||
Popconfirm,
|
||||
Spin,
|
||||
Tag,
|
||||
message,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
addDrugAlias,
|
||||
deleteDrugAlias,
|
||||
getDrugAliasDetail,
|
||||
} from './drug-alias-api';
|
||||
|
||||
type AliasRow = {
|
||||
id?: number;
|
||||
alias: string;
|
||||
pinyin_simple?: string;
|
||||
pinyin_full?: string;
|
||||
pinyin_display?: string;
|
||||
search_digits?: string;
|
||||
};
|
||||
|
||||
const loading = ref(false);
|
||||
const adding = ref(false);
|
||||
const deletingId = ref(0);
|
||||
const drug = ref<Record<string, any>>({});
|
||||
const aliases = ref<AliasRow[]>([]);
|
||||
const canManage = ref(false);
|
||||
/** 新增输入框 */
|
||||
const newAlias = ref('');
|
||||
const gridApiRef = ref<any>(null);
|
||||
|
||||
const titleText = computed(
|
||||
() => `药品详情${drug.value?.drug_name ? ` · ${drug.value.drug_name}` : ''}`,
|
||||
);
|
||||
|
||||
const aliasCountText = computed(() => `共 ${aliases.value.length} 条`);
|
||||
|
||||
/**
|
||||
* 用接口结果回填本地状态
|
||||
*/
|
||||
function applyDetail(res: any) {
|
||||
drug.value = res?.drug || {};
|
||||
aliases.value = Array.isArray(res?.aliases) ? res.aliases : [];
|
||||
canManage.value = !!res?.can_manage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载详情
|
||||
*/
|
||||
async function loadDetail(drugId: number) {
|
||||
loading.value = true;
|
||||
newAlias.value = '';
|
||||
try {
|
||||
const res = await getDrugAliasDetail(drugId);
|
||||
applyDetail(res);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
drug.value = {};
|
||||
aliases.value = [];
|
||||
canManage.value = false;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
class: 'w-[720px]',
|
||||
draggable: true,
|
||||
fullscreenButton: false,
|
||||
showConfirmButton: false,
|
||||
cancelText: '关闭',
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
newAlias.value = '';
|
||||
return;
|
||||
}
|
||||
const data = modalApi.getData<{
|
||||
drug_id?: number;
|
||||
id?: number;
|
||||
gridApi?: any;
|
||||
}>();
|
||||
gridApiRef.value = data?.gridApi || null;
|
||||
const id = Number(data?.drug_id || data?.id || 0);
|
||||
if (id > 0) {
|
||||
void loadDetail(id);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 添加一条别名(支持一次输入多个,按分隔符拆)
|
||||
*/
|
||||
async function handleAddAlias() {
|
||||
const id = Number(drug.value?.id || 0);
|
||||
if (!id || !canManage.value) return;
|
||||
const parts = String(newAlias.value || '')
|
||||
.split(/[\n,,、;;|]+/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
if (parts.length === 0) {
|
||||
message.warning('请输入别名');
|
||||
return;
|
||||
}
|
||||
adding.value = true;
|
||||
try {
|
||||
let lastRes: any = null;
|
||||
for (const part of parts) {
|
||||
lastRes = await addDrugAlias(id, part);
|
||||
}
|
||||
if (lastRes) applyDetail(lastRes);
|
||||
newAlias.value = '';
|
||||
message.success(parts.length > 1 ? `已添加 ${parts.length} 条别名` : '已添加');
|
||||
gridApiRef.value?.query?.();
|
||||
} finally {
|
||||
adding.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一条别名
|
||||
*/
|
||||
async function handleDeleteAlias(row: AliasRow) {
|
||||
const drugId = Number(drug.value?.id || 0);
|
||||
const aliasId = Number(row?.id || 0);
|
||||
if (!drugId || !aliasId || !canManage.value) return;
|
||||
deletingId.value = aliasId;
|
||||
try {
|
||||
const res = await deleteDrugAlias(drugId, aliasId);
|
||||
applyDetail(res);
|
||||
message.success('已删除');
|
||||
gridApiRef.value?.query?.();
|
||||
} finally {
|
||||
deletingId.value = 0;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="titleText">
|
||||
<Spin :spinning="loading">
|
||||
<div v-if="drug?.id" class="drug-detail">
|
||||
<div class="drug-detail__head">
|
||||
<Image
|
||||
v-if="drug.image"
|
||||
:src="drug.image"
|
||||
:width="64"
|
||||
:height="64"
|
||||
class="drug-detail__img"
|
||||
/>
|
||||
<div v-else class="drug-detail__img drug-detail__img--empty">无图</div>
|
||||
<div class="drug-detail__head-text">
|
||||
<div class="drug-detail__name">{{ drug.drug_name || '-' }}</div>
|
||||
<div class="drug-detail__meta">
|
||||
ID:{{ drug.id }}
|
||||
<span v-if="drug.drug_number"> · 编号:{{ drug.drug_number }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Descriptions bordered size="small" :column="2" class="mt-3">
|
||||
<DescriptionsItem label="拼音首拼">
|
||||
{{ drug.pinyin_simple || '-' }}
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="拼音全拼">
|
||||
{{ drug.pinyin_full || '-' }}
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="检索数字">
|
||||
{{ drug.search_digits || '-' }}
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="规格">
|
||||
{{ drug.specification || '-' }}
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="单位">
|
||||
{{ drug.unit_name || '-' }}
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="供应商">
|
||||
{{ drug.supplier_name || '-' }}
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
|
||||
<div class="drug-detail__alias-head">
|
||||
<div class="drug-detail__alias-title">
|
||||
别名
|
||||
<Tag v-if="canManage" color="processing" class="ml-1">可维护</Tag>
|
||||
<Tag v-else class="ml-1">只读</Tag>
|
||||
<span class="drug-detail__alias-count">{{ aliasCountText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 管理员:顶部添加区 -->
|
||||
<div v-if="canManage" class="drug-detail__alias-add">
|
||||
<Input
|
||||
v-model:value="newAlias"
|
||||
allow-clear
|
||||
placeholder="输入别名后回车添加;多个可用顿号/逗号分隔"
|
||||
:disabled="adding"
|
||||
@press-enter="handleAddAlias"
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
class="ml-2"
|
||||
:loading="adding"
|
||||
@click="handleAddAlias"
|
||||
>
|
||||
添加
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="drug-detail__alias-list">
|
||||
<Empty
|
||||
v-if="aliases.length === 0"
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
description="暂无别名"
|
||||
/>
|
||||
<div
|
||||
v-for="(a, idx) in aliases"
|
||||
:key="`${a.id || idx}_${a.alias}`"
|
||||
class="drug-detail__alias-row"
|
||||
>
|
||||
<div class="drug-detail__alias-main">
|
||||
<span class="drug-detail__alias-name">{{ a.alias }}</span>
|
||||
<span class="drug-detail__alias-py">
|
||||
{{ a.pinyin_display || a.pinyin_simple || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<Popconfirm
|
||||
v-if="canManage"
|
||||
title="确认删除该别名?"
|
||||
ok-text="删除"
|
||||
cancel-text="取消"
|
||||
@confirm="handleDeleteAlias(a)"
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
size="small"
|
||||
:loading="deletingId === Number(a.id || 0)"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Empty v-else-if="!loading" description="未找到药品" />
|
||||
</Spin>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.drug-detail__head {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.drug-detail__img {
|
||||
border-radius: 6px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.drug-detail__img--empty {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
.drug-detail__name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.drug-detail__meta {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
.drug-detail__alias-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.drug-detail__alias-title {
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.drug-detail__alias-count {
|
||||
margin-left: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
.drug-detail__alias-add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.drug-detail__alias-list {
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 6px;
|
||||
min-height: 80px;
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.drug-detail__alias-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid hsl(var(--border) / 0.7);
|
||||
font-size: 13px;
|
||||
}
|
||||
.drug-detail__alias-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.drug-detail__alias-main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.drug-detail__alias-name {
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
.drug-detail__alias-py {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,14 +1,24 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 商品信息合并列:图片 + 名称/ID/拼音/发布时间 + 说明书预览链接
|
||||
* 五类商品列表复用,避免每页各写一套 slot
|
||||
* 点击药名打开详情弹窗(由父级监听 name-click)
|
||||
* 搜索命中别名时展示匹配别名(对齐开方/仓库列表)
|
||||
*/
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { Image } from 'ant-design-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
row: Record<string, any>;
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
row: Record<string, any>;
|
||||
/** 药名是否可点击打开详情 */
|
||||
nameClickable?: boolean;
|
||||
}>(),
|
||||
{ nameClickable: true },
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
'name-click': [row: Record<string, any>];
|
||||
}>();
|
||||
|
||||
const previewVisible = ref(false);
|
||||
@@ -18,10 +28,27 @@ const instructionUrl = computed(() => {
|
||||
return typeof url === 'string' && url.trim() ? url.trim() : '';
|
||||
});
|
||||
|
||||
/** 当前搜索命中的别名 */
|
||||
const matchedAlias = computed(() =>
|
||||
String(props.row?.matched_alias || '').trim(),
|
||||
);
|
||||
|
||||
/** 拼音:命中别名时用别名拼音,否则用药名首拼 */
|
||||
const pinyinDisplay = computed(() => {
|
||||
const aliasPy = String(props.row?.matched_alias_pinyin || '').trim();
|
||||
if (matchedAlias.value && aliasPy) return aliasPy;
|
||||
return String(props.row?.pinyin_simple || '').trim();
|
||||
});
|
||||
|
||||
function openInstruction() {
|
||||
if (!instructionUrl.value) return;
|
||||
previewVisible.value = true;
|
||||
}
|
||||
|
||||
function onNameClick() {
|
||||
if (!props.nameClickable) return;
|
||||
emit('name-click', props.row);
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="product-info-cell">
|
||||
@@ -36,10 +63,25 @@ function openInstruction() {
|
||||
无图
|
||||
</div>
|
||||
<div class="product-info-cell__body">
|
||||
<div class="product-info-cell__name">{{ row.drug_name || '-' }}</div>
|
||||
<div
|
||||
class="product-info-cell__name"
|
||||
:class="{ 'product-info-cell__name--link': nameClickable }"
|
||||
:title="
|
||||
matchedAlias
|
||||
? `${row.drug_name || '-'}(别名:${matchedAlias})`
|
||||
: row.drug_name || '-'
|
||||
"
|
||||
@click.stop="onNameClick"
|
||||
>
|
||||
{{ row.drug_name || '-' }}
|
||||
</div>
|
||||
<div v-if="matchedAlias" class="product-info-cell__alias">
|
||||
别名:{{ matchedAlias }}
|
||||
<span v-if="pinyinDisplay">({{ pinyinDisplay }})</span>
|
||||
</div>
|
||||
<div class="product-info-cell__meta">
|
||||
<span>ID:{{ row.id }}</span>
|
||||
<span v-if="row.pinyin_simple">拼音:{{ row.pinyin_simple }}</span>
|
||||
<span v-if="pinyinDisplay && !matchedAlias">拼音:{{ pinyinDisplay }}</span>
|
||||
<span v-if="row.created_at">发布时间:{{ row.created_at }}</span>
|
||||
</div>
|
||||
<a
|
||||
@@ -49,7 +91,6 @@ function openInstruction() {
|
||||
>
|
||||
说明书
|
||||
</a>
|
||||
<!-- 隐藏 Image 仅用于说明书预览 -->
|
||||
<Image
|
||||
v-if="instructionUrl"
|
||||
:src="instructionUrl"
|
||||
@@ -96,6 +137,20 @@ function openInstruction() {
|
||||
line-height: 1.4;
|
||||
word-break: break-all;
|
||||
}
|
||||
.product-info-cell__name--link {
|
||||
color: hsl(var(--primary));
|
||||
cursor: pointer;
|
||||
}
|
||||
.product-info-cell__name--link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.product-info-cell__alias {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
line-height: 1.3;
|
||||
word-break: break-all;
|
||||
}
|
||||
.product-info-cell__meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -96,3 +96,10 @@ export async function applyBatchDrugNumberApi(data: {
|
||||
}) {
|
||||
return requestClient.post<any>(`${prefix}apply-batch-drug-number`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步空拼音(首拼/全拼/数字串)
|
||||
*/
|
||||
export async function syncChinaMedicinePinyin() {
|
||||
return requestClient.post<any>(`${prefix}sync-pinyin`, {});
|
||||
}
|
||||
|
||||
@@ -53,6 +53,33 @@ export const modalFormProps: VbenFormProps = {
|
||||
label: '药品别名',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '可留空,保存时按药名自动生成',
|
||||
},
|
||||
fieldName: 'pinyin_simple',
|
||||
formItemClass: 'col-span-6',
|
||||
label: '拼音首拼',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '可留空,保存时按药名自动生成',
|
||||
},
|
||||
fieldName: 'pinyin_full',
|
||||
formItemClass: 'col-span-6',
|
||||
label: '拼音全拼',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '可留空,保存时从药名提取数字',
|
||||
},
|
||||
fieldName: 'search_digits',
|
||||
formItemClass: 'col-span-6',
|
||||
label: '检索数字',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
|
||||
@@ -24,9 +24,11 @@ export const gridOptions: VxeGridProps<RowType> = {
|
||||
columns: [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
{ field: 'id', align: 'left', title: 'ID', width: 100 },
|
||||
{ field: 'drug_name', align: 'left', title: '药品名称' },
|
||||
{ field: 'drug_name', align: 'left', title: '药品名称', slots: { default: 'drug_name' } },
|
||||
{ field: 'drug_number', align: 'left', title: '药品编码(erp)' },
|
||||
{ field: 'pinyin_simple', title: '拼音' },
|
||||
{ field: 'pinyin_simple', title: '首拼', minWidth: 100, slots: { default: 'pinyin_simple' } },
|
||||
{ field: 'pinyin_full', title: '全拼', minWidth: 140 },
|
||||
{ field: 'search_digits', title: '数字', width: 90 },
|
||||
{ field: 'status', title: '状态', slots: { default: 'status' } },
|
||||
{ field: 'created_at', title: '发布时间' },
|
||||
{ type: 'html', title: '操作', slots: { default: 'action' } },
|
||||
|
||||
@@ -5,20 +5,22 @@ import { ref } from 'vue';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Image, message, Tag } from 'ant-design-vue';
|
||||
import { Button, Image, Modal as AntModal, message, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
import { downloadByData } from '#/util/tool';
|
||||
|
||||
import { deleteChinaMedicine, exportChinaMedicineApi } from './api';
|
||||
import { deleteChinaMedicine, exportChinaMedicineApi, syncChinaMedicinePinyin } from './api';
|
||||
import DrugNumberBatchModal from './components/DrugNumberBatchModal.vue';
|
||||
import FormModalDemo from './components/modal.vue';
|
||||
import ExcelUpload from './components/ExcelUpload.vue';
|
||||
import DrugDetailModal from '#/views/business/product/_shared/drug-detail-modal.vue';
|
||||
import { formOptions } from './config/search';
|
||||
import { gridOptions } from './config/table';
|
||||
|
||||
const hasTopTableDropDownActions = ref(false);
|
||||
const syncing = ref(false);
|
||||
|
||||
const gridEvents: VxeGridListeners<any> = {
|
||||
checkboxChange() {
|
||||
@@ -51,6 +53,10 @@ const [DrugNumberBatchModalComp, drugNumberBatchModalApi] = useVbenModal({
|
||||
connectedComponent: DrugNumberBatchModal,
|
||||
});
|
||||
|
||||
const [DrugDetailModalComp, drugDetailModalApi] = useVbenModal({
|
||||
connectedComponent: DrugDetailModal,
|
||||
});
|
||||
|
||||
const showModal = (data = {}, isUpdate = false) => {
|
||||
formModalApi.setData({
|
||||
// 表单值
|
||||
@@ -61,6 +67,36 @@ const showModal = (data = {}, isUpdate = false) => {
|
||||
formModalApi.open();
|
||||
};
|
||||
|
||||
/** 点击药名:打开详情 / 别名维护弹窗 */
|
||||
function openDrugDetail(row: any) {
|
||||
drugDetailModalApi.setData({
|
||||
drug_id: row?.id,
|
||||
gridApi,
|
||||
});
|
||||
drugDetailModalApi.open();
|
||||
}
|
||||
|
||||
/** 同步本类型空拼音(首拼/全拼/数字) */
|
||||
function handleSyncPinyin() {
|
||||
AntModal.confirm({
|
||||
title: '同步拼音',
|
||||
content:
|
||||
'将扫描本类型药品中首拼/全拼/数字串为空的记录并自动生成,已有值不会覆盖。确认执行?',
|
||||
onOk: async () => {
|
||||
syncing.value = true;
|
||||
try {
|
||||
const res = await syncChinaMedicinePinyin();
|
||||
message.success(
|
||||
`同步完成:更新 ${Number(res?.updated || 0)} 条,跳过 ${Number(res?.skipped || 0)} 条`,
|
||||
);
|
||||
gridApi.query();
|
||||
} finally {
|
||||
syncing.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const deleteApi = (row: any) => {
|
||||
let ids = [];
|
||||
if (row) {
|
||||
@@ -104,6 +140,7 @@ const openDrugNumberBatchModal = () => {
|
||||
<Page auto-content-height title="中药管理">
|
||||
<ExcelUploadModal />
|
||||
<DrugNumberBatchModalComp />
|
||||
<DrugDetailModalComp />
|
||||
<FormModal />
|
||||
<Grid>
|
||||
<template #toolbar-buttons>
|
||||
@@ -157,6 +194,30 @@ const openDrugNumberBatchModal = () => {
|
||||
</Button>
|
||||
</template>
|
||||
</TableAction>
|
||||
<Button class="ml-2" :loading="syncing" @click="handleSyncPinyin">
|
||||
同步拼音
|
||||
</Button>
|
||||
</template>
|
||||
<template #drug_name="{ row }">
|
||||
<div class="cm-drug-name-cell">
|
||||
<a class="text-primary cursor-pointer" @click="openDrugDetail(row)">
|
||||
{{ row.drug_name || '-' }}
|
||||
</a>
|
||||
<!-- 搜索命中别名时展示(对齐开方/仓库) -->
|
||||
<div v-if="row.matched_alias" class="cm-drug-name-cell__alias">
|
||||
别名:{{ row.matched_alias }}
|
||||
<span v-if="row.matched_alias_pinyin">
|
||||
({{ row.matched_alias_pinyin }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #pinyin_simple="{ row }">
|
||||
<span>
|
||||
{{
|
||||
row.matched_alias_pinyin || row.pinyin_simple || '-'
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<template #status="{ row }">
|
||||
<Tag :color="row.status_color">{{ row.status_txt }}</Tag>
|
||||
@@ -191,3 +252,12 @@ const openDrugNumberBatchModal = () => {
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
<style scoped>
|
||||
.cm-drug-name-cell__alias {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
line-height: 1.3;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -85,4 +85,11 @@ export async function importHealthFoodApi(data: Record<string, any>) {
|
||||
*/
|
||||
export async function updateZoneCategory(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}update-zone-category`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步空拼音(首拼/全拼/数字串)
|
||||
*/
|
||||
export async function syncHealthFoodPinyin() {
|
||||
return requestClient.post<any>(`${prefix}sync-pinyin`, {});
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import { ref } from 'vue';
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
// 导入Ant Design Vue组件
|
||||
import { Button, message, Tag } from 'ant-design-vue';
|
||||
import { Button, Modal as AntModal, message, Tag } from 'ant-design-vue';
|
||||
|
||||
// 导入Vben VxeGrid适配器
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
@@ -20,6 +20,7 @@ import { downloadByData } from '#/util/tool';
|
||||
import CategoryWarehouseCell from '#/views/business/product/_shared/category-warehouse-cell.vue';
|
||||
import CentralPriceCell from '#/views/business/product/_shared/central-price-cell.vue';
|
||||
import CentralStockInModal from '#/views/business/product/_shared/central-stock-in-modal.vue';
|
||||
import DrugDetailModal from '#/views/business/product/_shared/drug-detail-modal.vue';
|
||||
import DrugFreeShippingCell from '#/views/business/product/_shared/drug-free-shipping-cell.vue';
|
||||
import ProductInfoCell from '#/views/business/product/_shared/product-info-cell.vue';
|
||||
import WarehouseDrugPriceModal from '#/views/business/warehouse-drug-management/admin/components/modal.vue';
|
||||
@@ -27,7 +28,11 @@ import BindListModal from '#/views/system/delivery-warehouse-drug/components/bin
|
||||
import { resolveCentralPrice } from '#/views/system/delivery-warehouse-drug/utils/resolve-central-price';
|
||||
|
||||
// 导入保健食品管理相关API
|
||||
import { deleteHealthFood, exportHealthFoodApi } from './api';
|
||||
import {
|
||||
deleteHealthFood,
|
||||
exportHealthFoodApi,
|
||||
syncHealthFoodPinyin,
|
||||
} from './api';
|
||||
import FormModalDemo from './components/modal.vue';
|
||||
import ExcelUpload from './components/ExcelUpload.vue';
|
||||
import CategoryModal from './components/CategoryModal.vue';
|
||||
@@ -41,6 +46,7 @@ const PRODUCT_TYPE = 3;
|
||||
|
||||
// 控制顶部表格下拉操作按钮的显示状态
|
||||
const hasTopTableDropDownActions = ref(false);
|
||||
const syncing = ref(false);
|
||||
|
||||
// 定义表格事件监听器
|
||||
const gridEvents: VxeGridListeners<any> = {
|
||||
@@ -94,6 +100,40 @@ const [WarehousePriceModalComp, warehousePriceModalApi] = useVbenModal({
|
||||
connectedComponent: WarehouseDrugPriceModal,
|
||||
});
|
||||
|
||||
const [DrugDetailModalComp, drugDetailModalApi] = useVbenModal({
|
||||
connectedComponent: DrugDetailModal,
|
||||
});
|
||||
|
||||
/** 点击药名:打开详情 / 别名维护弹窗 */
|
||||
function openDrugDetail(row: any) {
|
||||
drugDetailModalApi.setData({
|
||||
drug_id: row?.id,
|
||||
gridApi,
|
||||
});
|
||||
drugDetailModalApi.open();
|
||||
}
|
||||
|
||||
/** 同步本类型空拼音(首拼/全拼/数字) */
|
||||
function handleSyncPinyin() {
|
||||
AntModal.confirm({
|
||||
title: '同步拼音',
|
||||
content:
|
||||
'将扫描本类型药品中首拼/全拼/数字串为空的记录并自动生成,已有值不会覆盖。确认执行?',
|
||||
onOk: async () => {
|
||||
syncing.value = true;
|
||||
try {
|
||||
const res = await syncHealthFoodPinyin();
|
||||
message.success(
|
||||
`同步完成:更新 ${Number(res?.updated || 0)} 条,跳过 ${Number(res?.skipped || 0)} 条`,
|
||||
);
|
||||
gridApi.query();
|
||||
} finally {
|
||||
syncing.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开某产品的配送仓绑定列表(带总仓售价供费用封顶/默认平台费)
|
||||
* @param autoCreate 为 true 时打开后立刻弹出新增绑定表单
|
||||
@@ -234,6 +274,7 @@ const openExcelUploadModal = () => {
|
||||
<Page auto-content-height title="保健食品管理">
|
||||
<ExcelUploadModal />
|
||||
<FormModal />
|
||||
<DrugDetailModalComp />
|
||||
<CategoryModalComp />
|
||||
<BindListModalComp />
|
||||
<CentralStockInModalComp />
|
||||
@@ -281,9 +322,12 @@ const openExcelUploadModal = () => {
|
||||
</Button>
|
||||
</template>
|
||||
</TableAction>
|
||||
<Button class="ml-2" :loading="syncing" @click="handleSyncPinyin">
|
||||
同步拼音
|
||||
</Button>
|
||||
</template>
|
||||
<template #product_info="{ row }">
|
||||
<ProductInfoCell :row="row" />
|
||||
<ProductInfoCell :row="row" @name-click="openDrugDetail" />
|
||||
</template>
|
||||
<template #category_warehouse="{ row }">
|
||||
<CategoryWarehouseCell
|
||||
|
||||
@@ -73,6 +73,13 @@ export async function updateZoneCategory(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}update-zone-category`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步空拼音(首拼/全拼/数字串)
|
||||
*/
|
||||
export async function syncMedicalDevicePinyin() {
|
||||
return requestClient.post<any>(`${prefix}sync-pinyin`, {});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ref } from 'vue';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, message, Tag } from 'ant-design-vue';
|
||||
import { Button, Modal as AntModal, message, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
@@ -13,13 +13,18 @@ import { downloadByData } from '#/util/tool';
|
||||
import CategoryWarehouseCell from '#/views/business/product/_shared/category-warehouse-cell.vue';
|
||||
import CentralPriceCell from '#/views/business/product/_shared/central-price-cell.vue';
|
||||
import CentralStockInModal from '#/views/business/product/_shared/central-stock-in-modal.vue';
|
||||
import DrugDetailModal from '#/views/business/product/_shared/drug-detail-modal.vue';
|
||||
import DrugFreeShippingCell from '#/views/business/product/_shared/drug-free-shipping-cell.vue';
|
||||
import ProductInfoCell from '#/views/business/product/_shared/product-info-cell.vue';
|
||||
import WarehouseDrugPriceModal from '#/views/business/warehouse-drug-management/admin/components/modal.vue';
|
||||
import BindListModal from '#/views/system/delivery-warehouse-drug/components/bind-list-modal.vue';
|
||||
import { resolveCentralPrice } from '#/views/system/delivery-warehouse-drug/utils/resolve-central-price';
|
||||
|
||||
import { deleteMedicalDevice, exportMedicalDeviceApi } from './api';
|
||||
import {
|
||||
deleteMedicalDevice,
|
||||
exportMedicalDeviceApi,
|
||||
syncMedicalDevicePinyin,
|
||||
} from './api';
|
||||
import FormModalDemo from './components/modal.vue';
|
||||
import ExcelUpload from './components/ExcelUpload.vue';
|
||||
import CategoryModal from './components/CategoryModal.vue';
|
||||
@@ -30,6 +35,7 @@ import { gridOptions } from './config/table';
|
||||
const PRODUCT_TYPE = 7;
|
||||
|
||||
const hasTopTableDropDownActions = ref(false);
|
||||
const syncing = ref(false);
|
||||
|
||||
const gridEvents: VxeGridListeners<any> = {
|
||||
checkboxChange() {
|
||||
@@ -72,6 +78,40 @@ const [WarehousePriceModalComp, warehousePriceModalApi] = useVbenModal({
|
||||
connectedComponent: WarehouseDrugPriceModal,
|
||||
});
|
||||
|
||||
const [DrugDetailModalComp, drugDetailModalApi] = useVbenModal({
|
||||
connectedComponent: DrugDetailModal,
|
||||
});
|
||||
|
||||
/** 点击药名:打开详情 / 别名维护弹窗 */
|
||||
function openDrugDetail(row: any) {
|
||||
drugDetailModalApi.setData({
|
||||
drug_id: row?.id,
|
||||
gridApi,
|
||||
});
|
||||
drugDetailModalApi.open();
|
||||
}
|
||||
|
||||
/** 同步本类型空拼音(首拼/全拼/数字) */
|
||||
function handleSyncPinyin() {
|
||||
AntModal.confirm({
|
||||
title: '同步拼音',
|
||||
content:
|
||||
'将扫描本类型药品中首拼/全拼/数字串为空的记录并自动生成,已有值不会覆盖。确认执行?',
|
||||
onOk: async () => {
|
||||
syncing.value = true;
|
||||
try {
|
||||
const res = await syncMedicalDevicePinyin();
|
||||
message.success(
|
||||
`同步完成:更新 ${Number(res?.updated || 0)} 条,跳过 ${Number(res?.skipped || 0)} 条`,
|
||||
);
|
||||
gridApi.query();
|
||||
} finally {
|
||||
syncing.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开某产品的配送仓绑定列表(带总仓售价供费用封顶/默认平台费)
|
||||
* @param autoCreate 为 true 时打开后立刻弹出新增绑定表单
|
||||
@@ -181,6 +221,7 @@ const openExcelUploadModal = () => {
|
||||
<Page auto-content-height title="医疗器械管理">
|
||||
<ExcelUploadModal />
|
||||
<FormModal />
|
||||
<DrugDetailModalComp />
|
||||
<CategoryModalComp />
|
||||
<BindListModalComp />
|
||||
<CentralStockInModalComp />
|
||||
@@ -228,9 +269,12 @@ const openExcelUploadModal = () => {
|
||||
</Button>
|
||||
</template>
|
||||
</TableAction>
|
||||
<Button class="ml-2" :loading="syncing" @click="handleSyncPinyin">
|
||||
同步拼音
|
||||
</Button>
|
||||
</template>
|
||||
<template #product_info="{ row }">
|
||||
<ProductInfoCell :row="row" />
|
||||
<ProductInfoCell :row="row" @name-click="openDrugDetail" />
|
||||
</template>
|
||||
<template #category_warehouse="{ row }">
|
||||
<CategoryWarehouseCell
|
||||
|
||||
@@ -73,6 +73,13 @@ export async function updateZoneCategory(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}update-zone-category`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步空拼音(首拼/全拼/数字串)
|
||||
*/
|
||||
export async function syncNonDrugPinyin() {
|
||||
return requestClient.post<any>(`${prefix}sync-pinyin`, {});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ref } from 'vue';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, message, Tag } from 'ant-design-vue';
|
||||
import { Button, Modal as AntModal, message, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
@@ -13,13 +13,14 @@ import { downloadByData } from '#/util/tool';
|
||||
import CategoryWarehouseCell from '#/views/business/product/_shared/category-warehouse-cell.vue';
|
||||
import CentralPriceCell from '#/views/business/product/_shared/central-price-cell.vue';
|
||||
import CentralStockInModal from '#/views/business/product/_shared/central-stock-in-modal.vue';
|
||||
import DrugDetailModal from '#/views/business/product/_shared/drug-detail-modal.vue';
|
||||
import DrugFreeShippingCell from '#/views/business/product/_shared/drug-free-shipping-cell.vue';
|
||||
import ProductInfoCell from '#/views/business/product/_shared/product-info-cell.vue';
|
||||
import WarehouseDrugPriceModal from '#/views/business/warehouse-drug-management/admin/components/modal.vue';
|
||||
import BindListModal from '#/views/system/delivery-warehouse-drug/components/bind-list-modal.vue';
|
||||
import { resolveCentralPrice } from '#/views/system/delivery-warehouse-drug/utils/resolve-central-price';
|
||||
|
||||
import { deleteNonDrug, exportNonDrugApi } from './api';
|
||||
import { deleteNonDrug, exportNonDrugApi, syncNonDrugPinyin } from './api';
|
||||
import FormModalDemo from './components/modal.vue';
|
||||
import ExcelUpload from './components/ExcelUpload.vue';
|
||||
import CategoryModal from './components/CategoryModal.vue';
|
||||
@@ -30,6 +31,7 @@ import { gridOptions } from './config/table';
|
||||
const PRODUCT_TYPE = 6;
|
||||
|
||||
const hasTopTableDropDownActions = ref(false);
|
||||
const syncing = ref(false);
|
||||
|
||||
const gridEvents: VxeGridListeners<any> = {
|
||||
checkboxChange() {
|
||||
@@ -72,6 +74,40 @@ const [WarehousePriceModalComp, warehousePriceModalApi] = useVbenModal({
|
||||
connectedComponent: WarehouseDrugPriceModal,
|
||||
});
|
||||
|
||||
const [DrugDetailModalComp, drugDetailModalApi] = useVbenModal({
|
||||
connectedComponent: DrugDetailModal,
|
||||
});
|
||||
|
||||
/** 点击药名:打开详情 / 别名维护弹窗 */
|
||||
function openDrugDetail(row: any) {
|
||||
drugDetailModalApi.setData({
|
||||
drug_id: row?.id,
|
||||
gridApi,
|
||||
});
|
||||
drugDetailModalApi.open();
|
||||
}
|
||||
|
||||
/** 同步本类型空拼音(首拼/全拼/数字) */
|
||||
function handleSyncPinyin() {
|
||||
AntModal.confirm({
|
||||
title: '同步拼音',
|
||||
content:
|
||||
'将扫描本类型药品中首拼/全拼/数字串为空的记录并自动生成,已有值不会覆盖。确认执行?',
|
||||
onOk: async () => {
|
||||
syncing.value = true;
|
||||
try {
|
||||
const res = await syncNonDrugPinyin();
|
||||
message.success(
|
||||
`同步完成:更新 ${Number(res?.updated || 0)} 条,跳过 ${Number(res?.skipped || 0)} 条`,
|
||||
);
|
||||
gridApi.query();
|
||||
} finally {
|
||||
syncing.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开某产品的配送仓绑定列表(带总仓售价供费用封顶/默认平台费)
|
||||
* @param autoCreate 为 true 时打开后立刻弹出新增绑定表单
|
||||
@@ -181,6 +217,7 @@ const openExcelUploadModal = () => {
|
||||
<Page auto-content-height title="非药品管理">
|
||||
<ExcelUploadModal />
|
||||
<FormModal />
|
||||
<DrugDetailModalComp />
|
||||
<CategoryModalComp />
|
||||
<BindListModalComp />
|
||||
<CentralStockInModalComp />
|
||||
@@ -228,9 +265,12 @@ const openExcelUploadModal = () => {
|
||||
</Button>
|
||||
</template>
|
||||
</TableAction>
|
||||
<Button class="ml-2" :loading="syncing" @click="handleSyncPinyin">
|
||||
同步拼音
|
||||
</Button>
|
||||
</template>
|
||||
<template #product_info="{ row }">
|
||||
<ProductInfoCell :row="row" />
|
||||
<ProductInfoCell :row="row" @name-click="openDrugDetail" />
|
||||
</template>
|
||||
<template #category_warehouse="{ row }">
|
||||
<CategoryWarehouseCell
|
||||
|
||||
@@ -55,3 +55,10 @@ export async function deleteServicePack(data: Record<string, any>) {
|
||||
export async function updateZoneCategory(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}update-zone-category`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步空拼音(首拼/全拼/数字串)
|
||||
*/
|
||||
export async function syncServicePackPinyin() {
|
||||
return requestClient.post<any>(`${prefix}sync-pinyin`, {});
|
||||
}
|
||||
|
||||
@@ -5,20 +5,21 @@ import { ref } from 'vue';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, message, Tag } from 'ant-design-vue';
|
||||
import { Button, Modal as AntModal, message, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
import CategoryWarehouseCell from '#/views/business/product/_shared/category-warehouse-cell.vue';
|
||||
import CentralPriceCell from '#/views/business/product/_shared/central-price-cell.vue';
|
||||
import CentralStockInModal from '#/views/business/product/_shared/central-stock-in-modal.vue';
|
||||
import DrugDetailModal from '#/views/business/product/_shared/drug-detail-modal.vue';
|
||||
import DrugFreeShippingCell from '#/views/business/product/_shared/drug-free-shipping-cell.vue';
|
||||
import ProductInfoCell from '#/views/business/product/_shared/product-info-cell.vue';
|
||||
import WarehouseDrugPriceModal from '#/views/business/warehouse-drug-management/admin/components/modal.vue';
|
||||
import BindListModal from '#/views/system/delivery-warehouse-drug/components/bind-list-modal.vue';
|
||||
import { resolveCentralPrice } from '#/views/system/delivery-warehouse-drug/utils/resolve-central-price';
|
||||
|
||||
import { deleteServicePack } from './api';
|
||||
import { deleteServicePack, syncServicePackPinyin } from './api';
|
||||
import FormModalDemo from './components/modal.vue';
|
||||
import ZoneCategoryModal from './components/ZoneCategoryModal.vue';
|
||||
import { formOptions } from './config/search';
|
||||
@@ -28,6 +29,7 @@ import { gridOptions } from './config/table';
|
||||
const PRODUCT_TYPE = 5;
|
||||
|
||||
const hasTopTableDropDownActions = ref(false);
|
||||
const syncing = ref(false);
|
||||
|
||||
const gridEvents: VxeGridListeners<any> = {
|
||||
checkboxChange() {
|
||||
@@ -68,6 +70,40 @@ const [WarehousePriceModalComp, warehousePriceModalApi] = useVbenModal({
|
||||
connectedComponent: WarehouseDrugPriceModal,
|
||||
});
|
||||
|
||||
const [DrugDetailModalComp, drugDetailModalApi] = useVbenModal({
|
||||
connectedComponent: DrugDetailModal,
|
||||
});
|
||||
|
||||
/** 点击药名:打开详情 / 别名维护弹窗 */
|
||||
function openDrugDetail(row: any) {
|
||||
drugDetailModalApi.setData({
|
||||
drug_id: row?.id,
|
||||
gridApi,
|
||||
});
|
||||
drugDetailModalApi.open();
|
||||
}
|
||||
|
||||
/** 同步本类型空拼音(首拼/全拼/数字) */
|
||||
function handleSyncPinyin() {
|
||||
AntModal.confirm({
|
||||
title: '同步拼音',
|
||||
content:
|
||||
'将扫描本类型药品中首拼/全拼/数字串为空的记录并自动生成,已有值不会覆盖。确认执行?',
|
||||
onOk: async () => {
|
||||
syncing.value = true;
|
||||
try {
|
||||
const res = await syncServicePackPinyin();
|
||||
message.success(
|
||||
`同步完成:更新 ${Number(res?.updated || 0)} 条,跳过 ${Number(res?.skipped || 0)} 条`,
|
||||
);
|
||||
gridApi.query();
|
||||
} finally {
|
||||
syncing.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开某产品的配送仓绑定列表(带总仓售价供费用封顶/默认平台费)
|
||||
* @param autoCreate 为 true 时打开后立刻弹出新增绑定表单
|
||||
@@ -164,6 +200,7 @@ const deleteApi = (row: any) => {
|
||||
<template>
|
||||
<Page auto-content-height title="产品服务包管理">
|
||||
<FormModal />
|
||||
<DrugDetailModalComp />
|
||||
<ZoneCategoryModalComp />
|
||||
<BindListModalComp />
|
||||
<CentralStockInModalComp />
|
||||
@@ -200,9 +237,12 @@ const deleteApi = (row: any) => {
|
||||
</Button>
|
||||
</template>
|
||||
</TableAction>
|
||||
<Button class="ml-2" :loading="syncing" @click="handleSyncPinyin">
|
||||
同步拼音
|
||||
</Button>
|
||||
</template>
|
||||
<template #product_info="{ row }">
|
||||
<ProductInfoCell :row="row" />
|
||||
<ProductInfoCell :row="row" @name-click="openDrugDetail" />
|
||||
</template>
|
||||
<template #category_warehouse="{ row }">
|
||||
<CategoryWarehouseCell
|
||||
|
||||
@@ -92,3 +92,10 @@ export async function updateMinAdjustPrice(data: {
|
||||
export async function updateErpQtyFactor(data: { id: number; erp_qty_factor: number }) {
|
||||
return requestClient.post<any>(`${prefix}update-erp-qty-factor`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步空拼音(首拼/全拼/数字串)
|
||||
*/
|
||||
export async function syncWesternMedicinePinyin() {
|
||||
return requestClient.post<any>(`${prefix}sync-pinyin`, {});
|
||||
}
|
||||
|
||||
@@ -134,6 +134,33 @@ export const modalFormProps: VbenFormProps = {
|
||||
label: '药品别名',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '可留空,保存时按药名自动生成',
|
||||
},
|
||||
fieldName: 'pinyin_simple',
|
||||
formItemClass: 'col-span-6',
|
||||
label: '拼音首拼',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '可留空,保存时按药名自动生成',
|
||||
},
|
||||
fieldName: 'pinyin_full',
|
||||
formItemClass: 'col-span-6',
|
||||
label: '拼音全拼',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '可留空,保存时从药名提取数字',
|
||||
},
|
||||
fieldName: 'search_digits',
|
||||
formItemClass: 'col-span-6',
|
||||
label: '检索数字',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ref } from 'vue';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, message, Tag } from 'ant-design-vue';
|
||||
import { Button, Modal as AntModal, message, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
@@ -13,13 +13,18 @@ import { downloadByData } from '#/util/tool';
|
||||
import CategoryWarehouseCell from '#/views/business/product/_shared/category-warehouse-cell.vue';
|
||||
import CentralPriceCell from '#/views/business/product/_shared/central-price-cell.vue';
|
||||
import CentralStockInModal from '#/views/business/product/_shared/central-stock-in-modal.vue';
|
||||
import DrugDetailModal from '#/views/business/product/_shared/drug-detail-modal.vue';
|
||||
import DrugFreeShippingCell from '#/views/business/product/_shared/drug-free-shipping-cell.vue';
|
||||
import ProductInfoCell from '#/views/business/product/_shared/product-info-cell.vue';
|
||||
import WarehouseDrugPriceModal from '#/views/business/warehouse-drug-management/admin/components/modal.vue';
|
||||
import BindListModal from '#/views/system/delivery-warehouse-drug/components/bind-list-modal.vue';
|
||||
import { resolveCentralPrice } from '#/views/system/delivery-warehouse-drug/utils/resolve-central-price';
|
||||
|
||||
import { deleteWesternMedicine, exportWesternMedicineApi } from './api';
|
||||
import {
|
||||
deleteWesternMedicine,
|
||||
exportWesternMedicineApi,
|
||||
syncWesternMedicinePinyin,
|
||||
} from './api';
|
||||
import FormModalDemo from './components/modal.vue';
|
||||
import ExcelUpload from './components/ExcelUpload.vue';
|
||||
import CategoryModal from './components/CategoryModal.vue';
|
||||
@@ -32,6 +37,7 @@ import { gridOptions } from './config/table';
|
||||
const PRODUCT_TYPE = 2;
|
||||
|
||||
const hasTopTableDropDownActions = ref(false);
|
||||
const syncing = ref(false);
|
||||
|
||||
const gridEvents: VxeGridListeners<any> = {
|
||||
checkboxChange() {
|
||||
@@ -84,6 +90,40 @@ const [WarehousePriceModalComp, warehousePriceModalApi] = useVbenModal({
|
||||
connectedComponent: WarehouseDrugPriceModal,
|
||||
});
|
||||
|
||||
const [DrugDetailModalComp, drugDetailModalApi] = useVbenModal({
|
||||
connectedComponent: DrugDetailModal,
|
||||
});
|
||||
|
||||
/** 点击药名:打开详情 / 别名维护弹窗 */
|
||||
function openDrugDetail(row: any) {
|
||||
drugDetailModalApi.setData({
|
||||
drug_id: row?.id,
|
||||
gridApi,
|
||||
});
|
||||
drugDetailModalApi.open();
|
||||
}
|
||||
|
||||
/** 同步本类型空拼音(首拼/全拼/数字) */
|
||||
function handleSyncPinyin() {
|
||||
AntModal.confirm({
|
||||
title: '同步拼音',
|
||||
content:
|
||||
'将扫描本类型药品中首拼/全拼/数字串为空的记录并自动生成,已有值不会覆盖。确认执行?',
|
||||
onOk: async () => {
|
||||
syncing.value = true;
|
||||
try {
|
||||
const res = await syncWesternMedicinePinyin();
|
||||
message.success(
|
||||
`同步完成:更新 ${Number(res?.updated || 0)} 条,跳过 ${Number(res?.skipped || 0)} 条`,
|
||||
);
|
||||
gridApi.query();
|
||||
} finally {
|
||||
syncing.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开某药的配送仓绑定列表(带总仓售价供费用封顶/默认平台费)
|
||||
* @param autoCreate 为 true 时打开后立刻弹出新增绑定表单
|
||||
@@ -221,6 +261,7 @@ const openExcelUploadModal = () => {
|
||||
<Page auto-content-height title="西(中成)药管理">
|
||||
<ExcelUploadModal />
|
||||
<FormModal />
|
||||
<DrugDetailModalComp />
|
||||
<CategoryModalComp />
|
||||
<MinPriceModalComp />
|
||||
<ErpQtyFactorModalComp />
|
||||
@@ -274,9 +315,12 @@ const openExcelUploadModal = () => {
|
||||
</Button>
|
||||
</template>
|
||||
</TableAction>
|
||||
<Button class="ml-2" :loading="syncing" @click="handleSyncPinyin">
|
||||
同步拼音
|
||||
</Button>
|
||||
</template>
|
||||
<template #product_info="{ row }">
|
||||
<ProductInfoCell :row="row" />
|
||||
<ProductInfoCell :row="row" @name-click="openDrugDetail" />
|
||||
</template>
|
||||
<template #category_warehouse="{ row }">
|
||||
<CategoryWarehouseCell
|
||||
|
||||
@@ -25,8 +25,8 @@ export function createGridOptions(productType: number): VxeGridProps<RowType> {
|
||||
columns: [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
{ field: 'id', align: 'left', title: 'ID', width: 100 },
|
||||
{ field: 'drug.drug_name', align: 'left', title: '药品名称' },
|
||||
{ field: 'drug.pinyin_simple', title: '拼音' },
|
||||
{ field: 'drug.drug_name', align: 'left', title: '药品名称', minWidth: 160, slots: { default: 'drug_name' } },
|
||||
{ field: 'drug.pinyin_simple', title: '拼音', minWidth: 120, slots: { default: 'drug_pinyin' } },
|
||||
{
|
||||
field: 'image',
|
||||
align: 'left',
|
||||
|
||||
@@ -13,6 +13,7 @@ import { TableAction } from '#/components/table-action';
|
||||
import { downloadByData } from '#/util/tool';
|
||||
|
||||
import { useWarehouseDrugTypeRoute } from '../shared/useWarehouseDrugTypeRoute';
|
||||
import WarehouseDrugNameCell from '../shared/components/WarehouseDrugNameCell.vue';
|
||||
import {
|
||||
deleteWarehouseDrugManagement,
|
||||
getWarehouseExportDataApi,
|
||||
@@ -286,6 +287,20 @@ function onSpreadsheetConfigSaved() {
|
||||
</template>
|
||||
</TableAction>
|
||||
</template>
|
||||
<!-- 药名/拼音:搜索命中别名时展示匹配别名(对齐开方) -->
|
||||
<template #drug_name="{ row }">
|
||||
<WarehouseDrugNameCell :row="row" />
|
||||
</template>
|
||||
<template #drug_pinyin="{ row }">
|
||||
<span>
|
||||
{{
|
||||
row.matched_alias_pinyin ||
|
||||
row.drug?.matched_alias_pinyin ||
|
||||
row.drug?.pinyin_simple ||
|
||||
'-'
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<template #image="{ row }">
|
||||
<Image
|
||||
v-if="rowProductImageSrc(row)"
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 仓库/药品列表药名单元格:搜索命中别名时展示「别名 + 拼音」(对齐开方气泡)
|
||||
*/
|
||||
defineProps<{
|
||||
row: Record<string, any>;
|
||||
}>();
|
||||
|
||||
function drugNameOf(row: Record<string, any>): string {
|
||||
return String(row?.drug?.drug_name || row?.drug_name || '').trim() || '-';
|
||||
}
|
||||
|
||||
function matchedAliasOf(row: Record<string, any>): string {
|
||||
return String(
|
||||
row?.matched_alias || row?.drug?.matched_alias || '',
|
||||
).trim();
|
||||
}
|
||||
|
||||
function pinyinOf(row: Record<string, any>): string {
|
||||
const aliasPy = String(
|
||||
row?.matched_alias_pinyin || row?.drug?.matched_alias_pinyin || '',
|
||||
).trim();
|
||||
if (matchedAliasOf(row) && aliasPy) return aliasPy;
|
||||
return String(row?.drug?.pinyin_simple || row?.pinyin_simple || '').trim();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wh-drug-name-cell">
|
||||
<div class="wh-drug-name-cell__name" :title="drugNameOf(row)">
|
||||
{{ drugNameOf(row) }}
|
||||
</div>
|
||||
<div v-if="matchedAliasOf(row)" class="wh-drug-name-cell__alias">
|
||||
别名:{{ matchedAliasOf(row) }}
|
||||
<span v-if="pinyinOf(row)" class="wh-drug-name-cell__py">
|
||||
({{ pinyinOf(row) }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.wh-drug-name-cell__name {
|
||||
font-weight: 500;
|
||||
line-height: 1.35;
|
||||
word-break: break-all;
|
||||
}
|
||||
.wh-drug-name-cell__alias {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
line-height: 1.3;
|
||||
word-break: break-all;
|
||||
}
|
||||
.wh-drug-name-cell__py {
|
||||
color: hsl(var(--muted-foreground) / 0.9);
|
||||
}
|
||||
</style>
|
||||
@@ -25,8 +25,8 @@ export function createGridOptions(productType: number): VxeGridProps<RowType> {
|
||||
columns: [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
{ field: 'id', align: 'left', title: 'ID', width: 100 },
|
||||
{ field: 'drug.drug_name', align: 'left', title: '药品名称' },
|
||||
{ field: 'drug.pinyin_simple', title: '拼音' },
|
||||
{ field: 'drug.drug_name', align: 'left', title: '药品名称', minWidth: 160, slots: { default: 'drug_name' } },
|
||||
{ field: 'drug.pinyin_simple', title: '拼音', minWidth: 120, slots: { default: 'drug_pinyin' } },
|
||||
{
|
||||
field: 'image',
|
||||
align: 'left',
|
||||
|
||||
@@ -12,6 +12,7 @@ import { TableAction } from '#/components/table-action';
|
||||
import { downloadByData } from '#/util/tool';
|
||||
|
||||
import { useWarehouseDrugTypeRoute } from '../shared/useWarehouseDrugTypeRoute';
|
||||
import WarehouseDrugNameCell from '../shared/components/WarehouseDrugNameCell.vue';
|
||||
import {
|
||||
checkSubscribeApi,
|
||||
deleteWarehouseDrugManagementStore,
|
||||
@@ -215,6 +216,20 @@ checkSubscribe();
|
||||
</template>
|
||||
</TableAction>
|
||||
</template>
|
||||
<!-- 药名/拼音:搜索命中别名时展示匹配别名(对齐开方) -->
|
||||
<template #drug_name="{ row }">
|
||||
<WarehouseDrugNameCell :row="row" />
|
||||
</template>
|
||||
<template #drug_pinyin="{ row }">
|
||||
<span>
|
||||
{{
|
||||
row.matched_alias_pinyin ||
|
||||
row.drug?.matched_alias_pinyin ||
|
||||
row.drug?.pinyin_simple ||
|
||||
'-'
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<template #image="{ row }">
|
||||
<Image
|
||||
:src="
|
||||
|
||||
@@ -25,8 +25,8 @@ export function createGridOptions(productType: number): VxeGridProps<RowType> {
|
||||
columns: [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
{ field: 'id', align: 'left', title: 'ID', width: 100 },
|
||||
{ field: 'drug.drug_name', align: 'left', title: '药品名称' },
|
||||
{ field: 'drug.pinyin_simple', title: '拼音' },
|
||||
{ field: 'drug.drug_name', align: 'left', title: '药品名称', minWidth: 160, slots: { default: 'drug_name' } },
|
||||
{ field: 'drug.pinyin_simple', title: '拼音', minWidth: 120, slots: { default: 'drug_pinyin' } },
|
||||
{
|
||||
field: 'image',
|
||||
align: 'left',
|
||||
|
||||
@@ -12,6 +12,7 @@ import { TableAction } from '#/components/table-action';
|
||||
import { downloadByData } from '#/util/tool';
|
||||
|
||||
import { useWarehouseDrugTypeRoute } from '../shared/useWarehouseDrugTypeRoute';
|
||||
import WarehouseDrugNameCell from '../shared/components/WarehouseDrugNameCell.vue';
|
||||
import {
|
||||
checkSubscribeApi,
|
||||
deleteWarehouseDrugManagementStore,
|
||||
@@ -234,6 +235,20 @@ checkSubscribe();
|
||||
</template>
|
||||
</TableAction>
|
||||
</template>
|
||||
<!-- 药名/拼音:搜索命中别名时展示匹配别名(对齐开方) -->
|
||||
<template #drug_name="{ row }">
|
||||
<WarehouseDrugNameCell :row="row" />
|
||||
</template>
|
||||
<template #drug_pinyin="{ row }">
|
||||
<span>
|
||||
{{
|
||||
row.matched_alias_pinyin ||
|
||||
row.drug?.matched_alias_pinyin ||
|
||||
row.drug?.pinyin_simple ||
|
||||
'-'
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<template #image="{ row }">
|
||||
<Image
|
||||
:src="
|
||||
|
||||
@@ -567,12 +567,18 @@ function openFieldExpand(fieldCode: string, label: string) {
|
||||
);
|
||||
}
|
||||
|
||||
/** 保存病历(同步诊断/医嘱) */
|
||||
/** 保存病历(同步诊断/医嘱;主诉必填) */
|
||||
async function handleSave() {
|
||||
if (!props.registerId) {
|
||||
message.warning('无挂号信息');
|
||||
return;
|
||||
}
|
||||
const chief = String(form.chief_complaint || '').trim();
|
||||
if (!chief) {
|
||||
message.warning('请填写主诉');
|
||||
focusModuleTextarea('chief_complaint');
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
await saveMedicalRecord({
|
||||
@@ -583,6 +589,8 @@ async function handleSave() {
|
||||
diagnosis: diagnosisModel.value,
|
||||
doctor_order: medicalAdviceModel.value,
|
||||
medicalAdvice: medicalAdviceModel.value,
|
||||
// 后端按此开关做主诉必填拦截
|
||||
validate_required: 1,
|
||||
});
|
||||
persistLocalDraft();
|
||||
message.success('病历已保存');
|
||||
@@ -742,12 +750,14 @@ function fieldBlock(
|
||||
label: string,
|
||||
textKey?: string,
|
||||
source?: 'entry' | 'tcm_disease' | 'tcm_syndrome' | 'tcm_method',
|
||||
required = false,
|
||||
) {
|
||||
return {
|
||||
fieldCode,
|
||||
label,
|
||||
textKey: textKey || fieldCode,
|
||||
source: source || 'entry',
|
||||
required,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -772,7 +782,7 @@ const tcmCaseSubRows = [
|
||||
* (中医病案单独置底全宽,不放进网格)
|
||||
*/
|
||||
const allNormalFields = [
|
||||
fieldBlock('chief_complaint', '主诉'),
|
||||
fieldBlock('chief_complaint', '主诉', undefined, undefined, true),
|
||||
fieldBlock('family_history', '家族史'),
|
||||
fieldBlock('present_illness', '现病史'),
|
||||
fieldBlock('epidemic_history', '流行病学史'),
|
||||
@@ -848,7 +858,10 @@ const visibleNormalFields = computed(() =>
|
||||
class="mr-field-block"
|
||||
>
|
||||
<div class="mr-field-head mb-1">
|
||||
<span class="mr-field-label">{{ f.label }}</span>
|
||||
<span class="mr-field-label">
|
||||
{{ f.label }}
|
||||
<em v-if="f.required" class="mr-field-required">*</em>
|
||||
</span>
|
||||
<!-- 诊断/医嘱走处方同源气泡;其余走病历词条 -->
|
||||
<div
|
||||
v-if="f.fieldCode === 'diagnosis'"
|
||||
@@ -1265,6 +1278,12 @@ const visibleNormalFields = computed(() =>
|
||||
white-space: nowrap;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
.mr-field-required {
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
color: #cf1322;
|
||||
margin-left: 2px;
|
||||
}
|
||||
/* 一级 label 前置色条:醒目分区 */
|
||||
.mr-field-label::before {
|
||||
content: '';
|
||||
|
||||
@@ -281,8 +281,10 @@ function updatePanelPlacement() {
|
||||
listViewportH.value = Math.max(100, panelMax - HINT_HEIGHT - HEAD_HEIGHT);
|
||||
|
||||
const PANEL_MIN_WIDTH = 560;
|
||||
// 在视口内尽量加宽,减少名称/词条正文被省略
|
||||
const PANEL_PREFERRED_WIDTH = Math.min(920, vw - PANEL_PAD * 2);
|
||||
const width = Math.min(
|
||||
Math.max(rect.width, PANEL_MIN_WIDTH),
|
||||
Math.max(rect.width, PANEL_MIN_WIDTH, PANEL_PREFERRED_WIDTH),
|
||||
vw - PANEL_PAD * 2,
|
||||
);
|
||||
let left = rect.left;
|
||||
@@ -1190,8 +1192,9 @@ defineExpose({
|
||||
.entry-keyword-bubble__head,
|
||||
.entry-keyword-bubble__row {
|
||||
display: grid;
|
||||
grid-template-columns: 90px 120px 1fr 72px;
|
||||
gap: 6px;
|
||||
/* 名称/拼音/正文加宽,备注收窄,减少词条正文被裁切 */
|
||||
grid-template-columns: minmax(130px, 1.1fr) minmax(150px, 1.2fr) minmax(200px, 2.2fr) minmax(88px, 0.9fr);
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
font-size: 13px;
|
||||
align-items: center;
|
||||
@@ -1199,15 +1202,15 @@ defineExpose({
|
||||
}
|
||||
.entry-keyword-bubble__head.has-score,
|
||||
.entry-keyword-bubble__row.has-score {
|
||||
grid-template-columns: 84px 110px 1fr 56px 48px;
|
||||
grid-template-columns: minmax(120px, 1fr) minmax(140px, 1.1fr) minmax(180px, 2fr) minmax(72px, 0.8fr) 52px;
|
||||
}
|
||||
.entry-keyword-bubble__head.has-action,
|
||||
.entry-keyword-bubble__row.has-action {
|
||||
grid-template-columns: 84px 110px 1fr 56px 72px;
|
||||
grid-template-columns: minmax(120px, 1fr) minmax(140px, 1.1fr) minmax(180px, 2fr) minmax(72px, 0.8fr) 76px;
|
||||
}
|
||||
.entry-keyword-bubble__head.has-score.has-action,
|
||||
.entry-keyword-bubble__row.has-score.has-action {
|
||||
grid-template-columns: 80px 100px 1fr 52px 44px 68px;
|
||||
grid-template-columns: minmax(110px, 0.95fr) minmax(130px, 1fr) minmax(160px, 1.9fr) minmax(64px, 0.7fr) 48px 72px;
|
||||
}
|
||||
.entry-keyword-bubble__head {
|
||||
flex-shrink: 0;
|
||||
@@ -1226,7 +1229,6 @@ defineExpose({
|
||||
color: hsl(var(--accent-foreground));
|
||||
}
|
||||
.col-title,
|
||||
.col-content,
|
||||
.col-remark,
|
||||
.col-py {
|
||||
overflow: hidden;
|
||||
@@ -1236,6 +1238,17 @@ defineExpose({
|
||||
.col-title {
|
||||
font-weight: 500;
|
||||
}
|
||||
/* 词条正文:最多两行,配合加宽面板尽量多露字 */
|
||||
.col-content {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.col-py {
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
|
||||
Reference in New Issue
Block a user