feat: 金方相关
This commit is contained in:
@@ -1168,9 +1168,13 @@ function openCommonPrescriptionModal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开金方导入(VIP:golden_formula)
|
* 打开金方导入(VIP:golden_formula);仅中药处方可用
|
||||||
*/
|
*/
|
||||||
function openGoldenFormulaModal() {
|
function openGoldenFormulaModal() {
|
||||||
|
if (Number(prescriptionStore.activeCategory) !== 1) {
|
||||||
|
message.warning('金方仅支持中药处方');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!canUseGoldenFormula.value) {
|
if (!canUseGoldenFormula.value) {
|
||||||
message.warning('当前门店未开通金方 VIP 功能');
|
message.warning('当前门店未开通金方 VIP 功能');
|
||||||
return;
|
return;
|
||||||
@@ -1590,7 +1594,7 @@ const cancelSaveCommonPrescription = () => {
|
|||||||
选择常用方
|
选择常用方
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
v-if="canUseGoldenFormula"
|
v-if="prescriptionStore.activeCategory === 1 && canUseGoldenFormula"
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
@click="openGoldenFormulaModal"
|
@click="openGoldenFormulaModal"
|
||||||
|
|||||||
@@ -499,16 +499,24 @@ export async function aiSavePrescriptionDrugSelectionApi(data: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 金方列表(VIP:golden_formula) */
|
/** 金方列表(VIP:golden_formula);支持 source_id 筛选 */
|
||||||
export async function goldenFormulaListApi(data: {
|
export async function goldenFormulaListApi(data: {
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
page?: number;
|
page?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
store_id?: number;
|
store_id?: number;
|
||||||
|
source_id?: number;
|
||||||
}) {
|
}) {
|
||||||
return requestClient.get<any>(`${prefix}golden-formula-list`, { params: data });
|
return requestClient.get<any>(`${prefix}golden-formula-list`, { params: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 金方来源下拉(导入弹窗筛选用) */
|
||||||
|
export async function goldenFormulaSourceOptionApi(data?: { store_id?: number }) {
|
||||||
|
return requestClient.get<any>(`${prefix}golden-formula-source-option`, {
|
||||||
|
params: data || {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** 金方详情 */
|
/** 金方详情 */
|
||||||
export async function goldenFormulaDetailApi(data: {
|
export async function goldenFormulaDetailApi(data: {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -526,3 +534,16 @@ export async function goldenFormulaApplyApi(data: {
|
|||||||
}) {
|
}) {
|
||||||
return requestClient.post<any>(`${prefix}golden-formula-apply`, data);
|
return requestClient.post<any>(`${prefix}golden-formula-apply`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 保存医生金方匹配选择(改选/确认导入) */
|
||||||
|
export async function goldenFormulaSaveMatchApi(data: {
|
||||||
|
id: number;
|
||||||
|
store_id?: number;
|
||||||
|
items: Array<{
|
||||||
|
drug_name: string;
|
||||||
|
selected_drug_id: number;
|
||||||
|
selected_drug_name?: string;
|
||||||
|
}>;
|
||||||
|
}) {
|
||||||
|
return requestClient.post<any>(`${prefix}golden-formula-save-match`, data);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
/**
|
/**
|
||||||
* 金方导入弹窗(PC 接诊 / 在线复诊共用)
|
* 金方导入弹窗(PC 接诊 / 在线复诊共用)
|
||||||
* - VIP:golden_formula
|
* - VIP:golden_formula
|
||||||
|
* - 支持来源筛选;异常方标记不可选;方名后展示绑定来源
|
||||||
* - 选方后调用药名对照,确认导入时抛出与 AI 出方相同结构的 rows
|
* - 选方后调用药名对照,确认导入时抛出与 AI 出方相同结构的 rows
|
||||||
*/
|
*/
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Empty,
|
Empty,
|
||||||
Input,
|
Input,
|
||||||
|
Select,
|
||||||
Spin,
|
Spin,
|
||||||
Tag,
|
Tag,
|
||||||
message,
|
message,
|
||||||
@@ -20,6 +22,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
goldenFormulaApplyApi,
|
goldenFormulaApplyApi,
|
||||||
goldenFormulaListApi,
|
goldenFormulaListApi,
|
||||||
|
goldenFormulaSaveMatchApi,
|
||||||
|
goldenFormulaSourceOptionApi,
|
||||||
} from '../api';
|
} from '../api';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -35,6 +39,9 @@ const emit = defineEmits<{
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const applying = ref(false);
|
const applying = ref(false);
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
|
/** 金方来源筛选(0=全部) */
|
||||||
|
const sourceId = ref<number | undefined>(undefined);
|
||||||
|
const sourceOptions = ref<Array<{ label: string; value: number }>>([]);
|
||||||
const list = ref<any[]>([]);
|
const list = ref<any[]>([]);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
const page = ref(1);
|
const page = ref(1);
|
||||||
@@ -57,6 +64,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
onOpenChange(isOpen: boolean) {
|
onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
keyword.value = '';
|
keyword.value = '';
|
||||||
|
sourceId.value = undefined;
|
||||||
list.value = [];
|
list.value = [];
|
||||||
matchResult.value = null;
|
matchResult.value = null;
|
||||||
selectedId.value = 0;
|
selectedId.value = 0;
|
||||||
@@ -73,10 +81,29 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
// activeCategory 1=中药 2=西药,与 ProductTypeEnum 一致
|
// activeCategory 1=中药 2=西药,与 ProductTypeEnum 一致
|
||||||
prescriptionType.value = Number(data.category || 1) === 2 ? 2 : 1;
|
prescriptionType.value = Number(data.category || 1) === 2 ? 2 : 1;
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
|
loadSources();
|
||||||
loadList();
|
loadList();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载金方来源下拉,供顶部筛选
|
||||||
|
*/
|
||||||
|
async function loadSources() {
|
||||||
|
try {
|
||||||
|
const res = await goldenFormulaSourceOptionApi({
|
||||||
|
store_id: storeId.value || undefined,
|
||||||
|
});
|
||||||
|
const rows = Array.isArray(res) ? res : res?.items || [];
|
||||||
|
sourceOptions.value = rows.map((r: any) => ({
|
||||||
|
label: r.name,
|
||||||
|
value: Number(r.id),
|
||||||
|
}));
|
||||||
|
} catch {
|
||||||
|
sourceOptions.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 加载金方列表 */
|
/** 加载金方列表 */
|
||||||
async function loadList() {
|
async function loadList() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -88,6 +115,7 @@ async function loadList() {
|
|||||||
page: page.value,
|
page: page.value,
|
||||||
pageSize,
|
pageSize,
|
||||||
store_id: storeId.value || undefined,
|
store_id: storeId.value || undefined,
|
||||||
|
source_id: sourceId.value || undefined,
|
||||||
});
|
});
|
||||||
list.value = res?.items || [];
|
list.value = res?.items || [];
|
||||||
total.value = Number(res?.total || 0);
|
total.value = Number(res?.total || 0);
|
||||||
@@ -103,10 +131,34 @@ function onSearch() {
|
|||||||
loadList();
|
loadList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 切换来源筛选后回到第一页重载 */
|
||||||
|
function onSourceChange() {
|
||||||
|
page.value = 1;
|
||||||
|
loadList();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选中金方并做药名对照
|
* 列表展示名:方名(来源);无来源则只显示方名
|
||||||
|
*/
|
||||||
|
function displayName(item: any): string {
|
||||||
|
const name = item?.name || '';
|
||||||
|
const source = item?.source_name || item?.source || '';
|
||||||
|
return source ? `${name}(${source})` : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 是否异常方(不可选) */
|
||||||
|
function isAbnormal(item: any): boolean {
|
||||||
|
return Number(item?.is_abnormal ?? 0) === 1 || Number(item?.selectable ?? 1) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选中金方并做药名对照;异常方直接提示不可选
|
||||||
*/
|
*/
|
||||||
async function handleSelect(row: any) {
|
async function handleSelect(row: any) {
|
||||||
|
if (isAbnormal(row)) {
|
||||||
|
message.warning('该金方为异常方,无法选择导入');
|
||||||
|
return;
|
||||||
|
}
|
||||||
selectedId.value = Number(row.id);
|
selectedId.value = Number(row.id);
|
||||||
applying.value = true;
|
applying.value = true;
|
||||||
matchResult.value = null;
|
matchResult.value = null;
|
||||||
@@ -117,7 +169,8 @@ async function handleSelect(row: any) {
|
|||||||
register_id: registerId.value || undefined,
|
register_id: registerId.value || undefined,
|
||||||
prescription_type: prescriptionType.value,
|
prescription_type: prescriptionType.value,
|
||||||
});
|
});
|
||||||
matchResult.value = res;
|
// 规范化:保证每行有 candidates + selected_drug_id,便于点选切换
|
||||||
|
matchResult.value = normalizeMatchPayload(res);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
message.error(e?.message || '对照失败');
|
message.error(e?.message || '对照失败');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -126,27 +179,121 @@ async function handleSelect(row: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 确认导入:把 matched 转成 AI 导入同构 rows(候选默认取第一项)
|
* 规范化对照结果:缺省 selected_drug_id 时取候选第一项
|
||||||
*/
|
*/
|
||||||
function handleConfirmImport() {
|
function normalizeMatchPayload(payload: any) {
|
||||||
|
if (!payload) return null;
|
||||||
|
const matched = (payload.matched || []).map((m: any) => {
|
||||||
|
const candidates = Array.isArray(m.candidates) ? m.candidates : [];
|
||||||
|
let sid = Number(m.selected_drug_id || 0);
|
||||||
|
if (!sid && candidates.length) {
|
||||||
|
sid = Number(candidates[0].drug_id || 0);
|
||||||
|
}
|
||||||
|
return { ...m, candidates, selected_drug_id: sid };
|
||||||
|
});
|
||||||
|
return { ...payload, matched };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组装当前对照行的匹配记忆 items,供落库
|
||||||
|
*/
|
||||||
|
function buildMatchSaveItems(matched: any[]) {
|
||||||
|
return matched
|
||||||
|
.map((m: any) => {
|
||||||
|
const candidates = m.candidates || [];
|
||||||
|
const sid = Number(m.selected_drug_id || 0);
|
||||||
|
const picked =
|
||||||
|
candidates.find((c: any) => Number(c.drug_id) === sid) ||
|
||||||
|
candidates[0] ||
|
||||||
|
{};
|
||||||
|
const drugId = Number(picked.drug_id || sid || 0);
|
||||||
|
const drugName = String(m.ai_name || '').trim();
|
||||||
|
if (!drugName || !drugId) return null;
|
||||||
|
return {
|
||||||
|
drug_name: drugName,
|
||||||
|
selected_drug_id: drugId,
|
||||||
|
selected_drug_name: String(picked.drug_name || ''),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean) as Array<{
|
||||||
|
drug_name: string;
|
||||||
|
selected_drug_id: number;
|
||||||
|
selected_drug_name?: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 持久化医生匹配记忆(失败不阻断导入)
|
||||||
|
*/
|
||||||
|
async function persistDoctorMatches(items: Array<{
|
||||||
|
drug_name: string;
|
||||||
|
selected_drug_id: number;
|
||||||
|
selected_drug_name?: string;
|
||||||
|
}>) {
|
||||||
|
if (!selectedId.value || !items.length) return;
|
||||||
|
try {
|
||||||
|
await goldenFormulaSaveMatchApi({
|
||||||
|
id: selectedId.value,
|
||||||
|
store_id: storeId.value || undefined,
|
||||||
|
items,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('金方匹配记忆保存失败', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点击候选药品:更新该行 selected_drug_id,并立即落库记忆
|
||||||
|
*/
|
||||||
|
async function onPickCandidate(mi: number, candidate: any) {
|
||||||
|
const list = matchResult.value?.matched;
|
||||||
|
if (!list || !list[mi] || !candidate) return;
|
||||||
|
const drugId = Number(candidate.drug_id || 0);
|
||||||
|
if (!drugId) return;
|
||||||
|
if (Number(list[mi].selected_drug_id) === drugId) return;
|
||||||
|
list[mi].selected_drug_id = drugId;
|
||||||
|
const drugName = String(list[mi].ai_name || '').trim();
|
||||||
|
if (!drugName) return;
|
||||||
|
await persistDoctorMatches([
|
||||||
|
{
|
||||||
|
drug_name: drugName,
|
||||||
|
selected_drug_id: drugId,
|
||||||
|
selected_drug_name: String(candidate.drug_name || ''),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 确认导入:把 matched 转成 AI 导入同构 rows(按当前选中候选)
|
||||||
|
* 剂量:直接用后端 apply 已处理好的 m.dose(有小数已进位到整数克),禁止前端再改
|
||||||
|
*/
|
||||||
|
async function handleConfirmImport() {
|
||||||
const matched = matchResult.value?.matched || [];
|
const matched = matchResult.value?.matched || [];
|
||||||
if (!matched.length) {
|
if (!matched.length) {
|
||||||
message.warning('没有可导入的已匹配药品');
|
message.warning('没有可导入的已匹配药品');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 确认导入前再整批落库,保证下次自动选中
|
||||||
|
await persistDoctorMatches(buildMatchSaveItems(matched));
|
||||||
const rows = matched.map((m: any) => {
|
const rows = matched.map((m: any) => {
|
||||||
const candidates = m.candidates || [];
|
const candidates = m.candidates || [];
|
||||||
const selectedId = Number(m.selected_drug_id || 0);
|
const selectedDrugId = Number(m.selected_drug_id || 0);
|
||||||
const candidate =
|
const candidate =
|
||||||
candidates.find((c: any) => Number(c.drug_id) === selectedId) ||
|
candidates.find((c: any) => Number(c.drug_id) === selectedDrugId) ||
|
||||||
candidates[0] ||
|
candidates[0] ||
|
||||||
{};
|
{};
|
||||||
return {
|
return {
|
||||||
ai_name: m.ai_name,
|
// 以下 || '' 兜底对齐 AI 出方协议(AiPrescriptionDrawer.vue onConfirmImport),
|
||||||
dose: m.dose,
|
// 避免 undefined 进入 handleImportAiPrescription 后:
|
||||||
unit: m.unit,
|
// - dose 为 undefined → parseFloat NaN → 回退 1,丢失真实剂量
|
||||||
usage: m.usage,
|
// - unit 为 undefined → 中药卡片单位显示异常
|
||||||
frequency: m.frequency,
|
// - usage 为 undefined → wayId 解析失败
|
||||||
|
ai_name: m.ai_name || '',
|
||||||
|
// 后端 enrich 已对原始剂量做小数进位(如 46.88→47)
|
||||||
|
dose: m.dose || '',
|
||||||
|
unit: m.unit || '',
|
||||||
|
usage: m.usage || '',
|
||||||
|
frequency: m.frequency || '',
|
||||||
candidate,
|
candidate,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -173,14 +320,23 @@ defineExpose({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Modal title="导入金方">
|
<Modal title="导入金方" class="w-[50%] h-[80%]">
|
||||||
<div class="flex gap-3" style="min-height: 420px">
|
<!-- 颜色一律用主题变量,禁止写死亮色底(暗色模式可读) -->
|
||||||
<div class="w-[340px] shrink-0 border-r pr-3">
|
<div class="gf-modal flex gap-3 text-foreground" style="min-height: 420px">
|
||||||
|
<div class="w-[340px] shrink-0 border-r border-border pr-3">
|
||||||
<div class="mb-2 flex gap-2">
|
<div class="mb-2 flex gap-2">
|
||||||
|
<Select
|
||||||
|
v-model:value="sourceId"
|
||||||
|
allow-clear
|
||||||
|
class="w-[120px] shrink-0"
|
||||||
|
placeholder="来源"
|
||||||
|
:options="sourceOptions"
|
||||||
|
@change="onSourceChange"
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
v-model:value="keyword"
|
v-model:value="keyword"
|
||||||
allow-clear
|
allow-clear
|
||||||
placeholder="名称/首拼/来源/速览"
|
placeholder="名称/首拼/全拼/来源/速览"
|
||||||
@press-enter="onSearch"
|
@press-enter="onSearch"
|
||||||
/>
|
/>
|
||||||
<Button type="primary" @click="onSearch">搜索</Button>
|
<Button type="primary" @click="onSearch">搜索</Button>
|
||||||
@@ -189,24 +345,31 @@ defineExpose({
|
|||||||
<div v-if="!list.length" class="py-8">
|
<div v-if="!list.length" class="py-8">
|
||||||
<Empty description="暂无金方" />
|
<Empty description="暂无金方" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="max-h-[360px] space-y-2 overflow-y-auto">
|
<div v-else class="max-h-[550px] space-y-2 overflow-y-auto">
|
||||||
<div
|
<div
|
||||||
v-for="item in list"
|
v-for="item in list"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="cursor-pointer rounded border p-2 transition hover:border-primary"
|
class="gf-item rounded border border-border bg-card p-2 transition"
|
||||||
:class="selectedId === item.id ? 'border-primary bg-primary/5' : ''"
|
:class="{
|
||||||
|
'gf-item--active': selectedId === item.id,
|
||||||
|
'gf-item--disabled': isAbnormal(item),
|
||||||
|
'cursor-pointer hover:border-primary': !isAbnormal(item),
|
||||||
|
}"
|
||||||
@click="handleSelect(item)"
|
@click="handleSelect(item)"
|
||||||
>
|
>
|
||||||
<div class="font-medium">{{ item.name }}</div>
|
<div class="font-medium text-foreground">
|
||||||
<div class="mt-1 text-xs text-gray-500 line-clamp-2">
|
{{ displayName(item) }}
|
||||||
{{ item.herb_overview || '—' }}
|
<Tag v-if="isAbnormal(item)" color="error" class="ml-1">异常</Tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1 flex flex-wrap gap-1">
|
<div class="mt-1 line-clamp-2 text-xs text-muted-foreground">
|
||||||
<Tag v-if="item.source" color="gold">{{ item.source }}</Tag>
|
{{ item.herb_overview || '—' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="total > pageSize" class="mt-2 text-center text-xs text-gray-400">
|
<div
|
||||||
|
v-if="total > pageSize"
|
||||||
|
class="mt-2 text-center text-xs text-muted-foreground"
|
||||||
|
>
|
||||||
共 {{ total }} 条(当前第 {{ page }} 页)
|
共 {{ total }} 条(当前第 {{ page }} 页)
|
||||||
<Button
|
<Button
|
||||||
v-if="page * pageSize < total"
|
v-if="page * pageSize < total"
|
||||||
@@ -225,15 +388,16 @@ defineExpose({
|
|||||||
<Empty description="请选择左侧金方进行药名对照" />
|
<Empty description="请选择左侧金方进行药名对照" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="mb-2 text-base font-medium">
|
<div class="mb-2 text-base font-medium text-foreground">
|
||||||
{{ matchResult.formula?.name }}
|
{{
|
||||||
<Tag v-if="matchResult.formula?.source" class="ml-2" color="gold">
|
matchResult.formula?.source_name || matchResult.formula?.source
|
||||||
{{ matchResult.formula.source }}
|
? `${matchResult.formula?.name || ''}(${matchResult.formula.source_name || matchResult.formula.source})`
|
||||||
</Tag>
|
: matchResult.formula?.name
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="matchResult.formula?.indication_translation || matchResult.formula?.indication_original"
|
v-if="matchResult.formula?.indication_translation || matchResult.formula?.indication_original"
|
||||||
class="mb-2 text-xs text-gray-500"
|
class="mb-2 text-xs text-muted-foreground"
|
||||||
>
|
>
|
||||||
主治:
|
主治:
|
||||||
{{
|
{{
|
||||||
@@ -243,31 +407,57 @@ defineExpose({
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="matchResult.formula?.original_formula"
|
v-if="matchResult.formula?.original_formula"
|
||||||
class="mb-2 max-h-24 overflow-y-auto rounded bg-amber-50 px-2 py-1 text-xs text-gray-700 whitespace-pre-wrap"
|
class="gf-original mb-2 max-h-24 overflow-y-auto rounded px-2 py-1 text-xs whitespace-pre-wrap"
|
||||||
>
|
>
|
||||||
<span class="font-medium text-amber-700">原方:</span>
|
<span class="gf-original__label font-medium">原方:</span>
|
||||||
{{ matchResult.formula.original_formula }}
|
{{ matchResult.formula.original_formula }}
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2 text-sm">
|
<div class="mb-2 text-sm text-foreground">
|
||||||
已匹配
|
已匹配
|
||||||
<Tag color="success">{{ (matchResult.matched || []).length }}</Tag>
|
<Tag color="success">{{ (matchResult.matched || []).length }}</Tag>
|
||||||
未匹配
|
未匹配
|
||||||
<Tag color="warning">{{ (matchResult.unmatched || []).length }}</Tag>
|
<Tag color="warning">{{ (matchResult.unmatched || []).length }}</Tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-h-[280px] space-y-1 overflow-y-auto text-sm">
|
<div class="max-h-[280px] space-y-2 overflow-y-auto text-sm">
|
||||||
|
<!-- 与 AI 出方一致:一行一药 + 全部候选 Tag 可点选 -->
|
||||||
<div
|
<div
|
||||||
v-for="(m, idx) in matchResult.matched || []"
|
v-for="(m, mi) in matchResult.matched || []"
|
||||||
:key="'m' + idx"
|
:key="'m' + mi"
|
||||||
class="rounded bg-green-50 px-2 py-1"
|
class="gf-matched rounded px-2.5 py-2"
|
||||||
>
|
>
|
||||||
{{ m.show_dose || m.display_text || `${m.ai_name} ${m.dose || ''}${m.unit || ''}${m.usage ? `(${m.usage})` : ''}` }}
|
<div class="font-medium text-foreground">
|
||||||
→
|
{{
|
||||||
{{ (m.candidates && m.candidates[0] && m.candidates[0].drug_name) || '—' }}
|
m.show_dose ||
|
||||||
|
m.display_text ||
|
||||||
|
`${m.ai_name} ${m.dose || ''}${m.unit || ''}${m.usage ? `(${m.usage})` : ''}`
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="mt-1.5 flex flex-wrap gap-1">
|
||||||
|
<Tag
|
||||||
|
v-for="c in m.candidates || []"
|
||||||
|
:key="c.drug_id"
|
||||||
|
class="cursor-pointer !m-0 !text-[11px]"
|
||||||
|
:color="
|
||||||
|
Number(m.selected_drug_id) === Number(c.drug_id)
|
||||||
|
? 'success'
|
||||||
|
: undefined
|
||||||
|
"
|
||||||
|
@click="onPickCandidate(mi, c)"
|
||||||
|
>
|
||||||
|
{{ c.drug_name }}
|
||||||
|
</Tag>
|
||||||
|
<span
|
||||||
|
v-if="!(m.candidates || []).length"
|
||||||
|
class="text-xs text-muted-foreground"
|
||||||
|
>
|
||||||
|
无候选
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="(u, idx) in matchResult.unmatched || []"
|
v-for="(u, idx) in matchResult.unmatched || []"
|
||||||
:key="'u' + idx"
|
:key="'u' + idx"
|
||||||
class="rounded bg-orange-50 px-2 py-1"
|
class="gf-unmatched rounded px-2 py-1"
|
||||||
>
|
>
|
||||||
{{ u.show_dose || u.display_text || `${u.ai_name} ${u.dose || ''}${u.unit || ''}` }}
|
{{ u.show_dose || u.display_text || `${u.ai_name} ${u.dose || ''}${u.unit || ''}` }}
|
||||||
(未匹配)
|
(未匹配)
|
||||||
@@ -288,3 +478,37 @@ defineExpose({
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
/* 选中项:primary 透明度,暗色自动跟主题 */
|
||||||
|
.gf-item--active {
|
||||||
|
border-color: hsl(var(--primary)) !important;
|
||||||
|
background-color: hsl(var(--primary) / 0.1);
|
||||||
|
}
|
||||||
|
/* 异常方:灰显 + 禁止指针,表达不可选 */
|
||||||
|
.gf-item--disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.55;
|
||||||
|
background-color: hsl(var(--muted) / 0.35);
|
||||||
|
}
|
||||||
|
/* 原方展示区:用 warning 变量,禁止 bg-amber-50 / text-amber-700 */
|
||||||
|
.gf-original {
|
||||||
|
background-color: hsl(var(--warning) / 0.12);
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
border: 1px solid hsl(var(--warning) / 0.35);
|
||||||
|
}
|
||||||
|
.gf-original__label {
|
||||||
|
color: hsl(var(--warning));
|
||||||
|
}
|
||||||
|
/* 已匹配:success/primary 浅绿底;暗色下半透明 */
|
||||||
|
.gf-matched {
|
||||||
|
background-color: hsl(var(--primary) / 0.12);
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
border: 1px solid hsl(var(--primary) / 0.25);
|
||||||
|
}
|
||||||
|
/* 未匹配:warning 橙底 */
|
||||||
|
.gf-unmatched {
|
||||||
|
background-color: hsl(var(--warning) / 0.12);
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
border: 1px solid hsl(var(--warning) / 0.35);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1337,9 +1337,13 @@ function openCommonPrescriptionModal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 打开金方导入弹窗(VIP:golden_formula)
|
* 打开金方导入弹窗(VIP:golden_formula);仅中药处方可用
|
||||||
*/
|
*/
|
||||||
function openGoldenFormulaModal() {
|
function openGoldenFormulaModal() {
|
||||||
|
if (Number(activeCategory.value) !== 1) {
|
||||||
|
message.warning('金方仅支持中药处方');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!canUseGoldenFormula.value) {
|
if (!canUseGoldenFormula.value) {
|
||||||
message.warning('当前门店未开通金方 VIP 功能');
|
message.warning('当前门店未开通金方 VIP 功能');
|
||||||
return;
|
return;
|
||||||
@@ -3330,7 +3334,7 @@ function onStoreSelectOpenChange(open: boolean) {
|
|||||||
选择常用方
|
选择常用方
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
v-if="!isSpecialPrescriptionCartLocked && canUseGoldenFormula"
|
v-if="activeCategory === 1 && !isSpecialPrescriptionCartLocked && canUseGoldenFormula"
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
@click="openGoldenFormulaModal"
|
@click="openGoldenFormulaModal"
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { requestClient } from '#/api/request';
|
|||||||
/** 金方库管理 API */
|
/** 金方库管理 API */
|
||||||
const prefix = 'golden-formula/';
|
const prefix = 'golden-formula/';
|
||||||
|
|
||||||
|
/** 金方来源字典 API */
|
||||||
|
const sourcePrefix = 'golden-formula-source/';
|
||||||
|
|
||||||
export async function getGoldenFormulaList(data: any) {
|
export async function getGoldenFormulaList(data: any) {
|
||||||
return requestClient.get<any>(`${prefix}list`, { params: data });
|
return requestClient.get<any>(`${prefix}list`, { params: data });
|
||||||
}
|
}
|
||||||
@@ -23,7 +26,32 @@ export async function deleteGoldenFormula(data: Record<string, any>) {
|
|||||||
return requestClient.post<any>(`${prefix}delete`, data);
|
return requestClient.post<any>(`${prefix}delete`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Excel 解析后的行批量导入 */
|
/** Excel 解析后的行批量导入(带全局金方来源 source_id) */
|
||||||
export async function importGoldenFormula(data: { rows: Record<string, any>[] }) {
|
export async function importGoldenFormula(data: {
|
||||||
|
rows: Record<string, any>[];
|
||||||
|
source_id?: number;
|
||||||
|
}) {
|
||||||
return requestClient.post<any>(`${prefix}import`, data);
|
return requestClient.post<any>(`${prefix}import`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 药品存在性排查(全局中药饮片 type=1 + 别名表)
|
||||||
|
* 预览页打开时调用,返回存在的药名 + 不存在的药名
|
||||||
|
*/
|
||||||
|
export async function checkGoldenFormulaDrugs(data: { drug_names: string[] }) {
|
||||||
|
return requestClient.post<{ exists: string[]; missing: string[] }>(
|
||||||
|
`${prefix}check-drugs`,
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 金方来源字典 - 下拉选项 */
|
||||||
|
export async function getGoldenFormulaSourceOption() {
|
||||||
|
return requestClient.get<any[]>(`${sourcePrefix}option`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 金方来源字典 - 新增来源(预览页输入框新增) */
|
||||||
|
export async function createGoldenFormulaSource(data: { name: string }) {
|
||||||
|
return requestClient.post<any>(`${sourcePrefix}create`, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
/**
|
||||||
|
* 金方导入预览弹窗
|
||||||
|
* - 解析后的行在这里表格预览
|
||||||
|
* - 顶部全局「金方来源」选择器:下拉选已有 + 输入新增,所有行共用同一 source_id
|
||||||
|
* - 打开后批量排查药品存在性(中药饮片 type=1 + 别名表),表格加「不存在的药品」列红字标记
|
||||||
|
* - 确认导入时 emit { rows, sourceId },sourceId 为空提示选择来源
|
||||||
|
*/
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
Table,
|
||||||
|
Tag,
|
||||||
|
Tooltip,
|
||||||
|
message,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
checkGoldenFormulaDrugs,
|
||||||
|
createGoldenFormulaSource,
|
||||||
|
getGoldenFormulaSourceOption,
|
||||||
|
} from '../api';
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'confirm', payload: { rows: any[]; sourceId: number }): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 预览的原始行(Excel 解析后)
|
||||||
|
const rows = ref<any[]>([]);
|
||||||
|
// 金方来源下拉选项
|
||||||
|
const sourceOptions = ref<{ label: string; value: number }[]>([]);
|
||||||
|
// 选中的金方来源 id
|
||||||
|
const sourceId = ref<number>(0);
|
||||||
|
// 新增来源输入框文案
|
||||||
|
const newSourceName = ref('');
|
||||||
|
// 来源下拉 loading
|
||||||
|
const sourceLoading = ref(false);
|
||||||
|
// 新增来源提交中
|
||||||
|
const creatingSource = ref(false);
|
||||||
|
// 药品排查:不存在的药名集合
|
||||||
|
const missingSet = ref<Set<string>>(new Set());
|
||||||
|
// 排查中
|
||||||
|
const checking = ref(false);
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
fullscreenButton: false,
|
||||||
|
draggable: true,
|
||||||
|
class: 'w-[90%]',
|
||||||
|
onCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
onConfirm: async () => {
|
||||||
|
if (sourceId.value <= 0) {
|
||||||
|
message.warning('请选择金方来源');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!rows.value.length) {
|
||||||
|
message.warning('没有可导入的数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit('confirm', { rows: rows.value, sourceId: sourceId.value });
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
// 关闭时重置状态
|
||||||
|
rows.value = [];
|
||||||
|
sourceId.value = 0;
|
||||||
|
newSourceName.value = '';
|
||||||
|
missingSet.value = new Set();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = modalApi.getData<{ rows?: any[] }>() || {};
|
||||||
|
rows.value = Array.isArray(data.rows) ? data.rows : [];
|
||||||
|
await loadSourceOptions();
|
||||||
|
// 打开后批量排查药品存在性
|
||||||
|
await runDrugCheck();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 加载金方来源下拉 */
|
||||||
|
async function loadSourceOptions() {
|
||||||
|
sourceLoading.value = true;
|
||||||
|
try {
|
||||||
|
const list = await getGoldenFormulaSourceOption();
|
||||||
|
sourceOptions.value = (Array.isArray(list) ? list : []).map((item: any) => ({
|
||||||
|
label: String(item.name || ''),
|
||||||
|
value: Number(item.id || 0),
|
||||||
|
}));
|
||||||
|
} catch (e: any) {
|
||||||
|
sourceOptions.value = [];
|
||||||
|
message.error(e?.message || '加载金方来源失败');
|
||||||
|
} finally {
|
||||||
|
sourceLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新增来源:成功后刷新下拉并选中 */
|
||||||
|
async function handleCreateSource() {
|
||||||
|
const name = newSourceName.value.trim();
|
||||||
|
if (!name) {
|
||||||
|
message.warning('请输入来源名称');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 已存在同名则直接选中
|
||||||
|
const exist = sourceOptions.value.find(
|
||||||
|
(o) => o.label === name || o.label.includes(name),
|
||||||
|
);
|
||||||
|
if (exist) {
|
||||||
|
sourceId.value = exist.value;
|
||||||
|
newSourceName.value = '';
|
||||||
|
message.success('已存在该来源,已自动选中');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
creatingSource.value = true;
|
||||||
|
try {
|
||||||
|
const res = await createGoldenFormulaSource({ name });
|
||||||
|
const newId = Number(res?.id || 0);
|
||||||
|
if (newId > 0) {
|
||||||
|
await loadSourceOptions();
|
||||||
|
sourceId.value = newId;
|
||||||
|
newSourceName.value = '';
|
||||||
|
message.success('来源新增成功');
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e?.message || '新增来源失败');
|
||||||
|
} finally {
|
||||||
|
creatingSource.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从所有行的 drugs_json 收集药名,去重后批量排查存在性
|
||||||
|
*/
|
||||||
|
async function runDrugCheck() {
|
||||||
|
if (!rows.value.length) {
|
||||||
|
missingSet.value = new Set();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nameSet = new Set<string>();
|
||||||
|
for (const row of rows.value) {
|
||||||
|
const raw = row?.drugs_json;
|
||||||
|
if (!raw) continue;
|
||||||
|
try {
|
||||||
|
const arr = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||||
|
if (Array.isArray(arr)) {
|
||||||
|
for (const item of arr) {
|
||||||
|
const n = String(item?.name || item?.drug_name || '').trim();
|
||||||
|
if (n) nameSet.add(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// JSON 非法则跳过该行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const names = Array.from(nameSet);
|
||||||
|
if (!names.length) {
|
||||||
|
missingSet.value = new Set();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checking.value = true;
|
||||||
|
try {
|
||||||
|
const res = await checkGoldenFormulaDrugs({ drug_names: names });
|
||||||
|
const missing = Array.isArray(res?.missing) ? res.missing : [];
|
||||||
|
missingSet.value = new Set(missing);
|
||||||
|
} catch (e: any) {
|
||||||
|
// 排查失败不阻塞预览
|
||||||
|
missingSet.value = new Set();
|
||||||
|
} finally {
|
||||||
|
checking.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 计算单行的「不存在的药品」列表(该行药名 ∩ 缺失集合) */
|
||||||
|
function rowMissingDrugs(row: any): string[] {
|
||||||
|
const raw = row?.drugs_json;
|
||||||
|
if (!raw) return [];
|
||||||
|
let arr: any[] = [];
|
||||||
|
try {
|
||||||
|
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||||
|
if (Array.isArray(parsed)) arr = parsed;
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const result: string[] = [];
|
||||||
|
for (const item of arr) {
|
||||||
|
const n = String(item?.name || item?.drug_name || '').trim();
|
||||||
|
if (n && missingSet.value.has(n)) result.push(n);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 汇总:不存在的药品总数(用于顶部提示) */
|
||||||
|
const missingSummary = computed(() => Array.from(missingSet.value));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判定单行是否异常处方
|
||||||
|
* - drugs_json 解析后是对象且含 error(如 {error:无数据})
|
||||||
|
* - 或规范化后药味数为 0(空数组 / 无法解析出有效药名)
|
||||||
|
*/
|
||||||
|
function isAbnormalRow(row: any): boolean {
|
||||||
|
const raw = row?.drugs_json;
|
||||||
|
if (raw === null || raw === undefined || raw === '') return true;
|
||||||
|
let parsed: any;
|
||||||
|
try {
|
||||||
|
parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||||
|
} catch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!parsed || typeof parsed !== 'object') return true;
|
||||||
|
// 关联对象含 error:典型 {"error":"无数据"}
|
||||||
|
if (!Array.isArray(parsed) && Object.prototype.hasOwnProperty.call(parsed, 'error')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const list = Array.isArray(parsed)
|
||||||
|
? parsed
|
||||||
|
: Array.isArray(parsed?.drugs)
|
||||||
|
? parsed.drugs
|
||||||
|
: Array.isArray(parsed?.items)
|
||||||
|
? parsed.items
|
||||||
|
: [];
|
||||||
|
// 药味数为 0
|
||||||
|
const count = list.filter(
|
||||||
|
(item: any) => String(item?.name || item?.drug_name || '').trim() !== '',
|
||||||
|
).length;
|
||||||
|
return count === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 异常处方数量(顶部提示用) */
|
||||||
|
const abnormalCount = computed(
|
||||||
|
() => rows.value.filter((r) => isAbnormalRow(r)).length,
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Modal title="金方导入预览">
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<!-- 顶部:金方来源全局选择器 + 新增 -->
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<span class="text-sm font-medium">金方来源:</span>
|
||||||
|
<Select
|
||||||
|
v-model:value="sourceId"
|
||||||
|
:options="sourceOptions"
|
||||||
|
:loading="sourceLoading"
|
||||||
|
show-search
|
||||||
|
option-filter-prop="label"
|
||||||
|
placeholder="选择已有金方来源"
|
||||||
|
style="min-width: 220px"
|
||||||
|
/>
|
||||||
|
<span class="text-xs text-muted-foreground">或新增:</span>
|
||||||
|
<Input
|
||||||
|
v-model:value="newSourceName"
|
||||||
|
placeholder="输入新来源名称"
|
||||||
|
style="width: 200px"
|
||||||
|
@press-enter="handleCreateSource"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
:loading="creatingSource"
|
||||||
|
size="small"
|
||||||
|
@click="handleCreateSource"
|
||||||
|
>
|
||||||
|
新增来源
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 异常处方提示(仅警告,不拦截) -->
|
||||||
|
<div
|
||||||
|
v-if="abnormalCount > 0"
|
||||||
|
class="rounded-lg border border-red-500/35 bg-red-500/10 px-3 py-2 text-xs"
|
||||||
|
>
|
||||||
|
<span class="font-medium">⚠ 异常处方:</span>
|
||||||
|
共
|
||||||
|
<Tag color="error" class="!mx-1">{{ abnormalCount }}</Tag>
|
||||||
|
条(药品JSON含 error 或药味数为 0),仍可导入,入库后标记为异常
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 药品存在性排查提示(仅警告,不拦截) -->
|
||||||
|
<div
|
||||||
|
v-if="missingSummary.length"
|
||||||
|
class="rounded-lg border border-orange-500/35 bg-orange-500/10 px-3 py-2 text-xs"
|
||||||
|
>
|
||||||
|
<span class="font-medium">⚠ 药品排查:</span>
|
||||||
|
共发现
|
||||||
|
<Tag color="orange" class="!mx-1">{{ missingSummary.length }}</Tag>
|
||||||
|
个不存在的药品(仅提示,不拦截导入):
|
||||||
|
<span class="text-orange-700">{{ missingSummary.join('、') }}</span>
|
||||||
|
<span v-if="checking" class="ml-2 text-muted-foreground">排查中…</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 预览表格 -->
|
||||||
|
<Table
|
||||||
|
:columns="[
|
||||||
|
{ title: '序号', width: 60, customRender: ({ index }) => index + 1 },
|
||||||
|
{ title: '药方名字', dataIndex: 'name', width: 140 },
|
||||||
|
{ title: '条文编号', dataIndex: 'clause_number', width: 110 },
|
||||||
|
{ title: '状态', key: 'abnormal', width: 90 },
|
||||||
|
{
|
||||||
|
title: '药品JSON',
|
||||||
|
dataIndex: 'drugs_json',
|
||||||
|
ellipsis: true,
|
||||||
|
customRender: ({ text }) => String(text || ''),
|
||||||
|
},
|
||||||
|
{ title: '药材速览', dataIndex: 'herb_overview', width: 180, ellipsis: true },
|
||||||
|
{
|
||||||
|
title: '药方主治(文言文)',
|
||||||
|
dataIndex: 'indication_original',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '现代主治(白话文)',
|
||||||
|
dataIndex: 'indication_translation',
|
||||||
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{ title: '拼音首拼', dataIndex: 'pinyin_initials', width: 100 },
|
||||||
|
{ title: '拼音全拼', dataIndex: 'pinyin', width: 120 },
|
||||||
|
{ title: '不存在的药品', key: 'missing', width: 180 },
|
||||||
|
]"
|
||||||
|
:data-source="rows"
|
||||||
|
:pagination="{ pageSize: 10, showSizeChanger: true }"
|
||||||
|
size="small"
|
||||||
|
:scroll="{ x: 1400 }"
|
||||||
|
:row-class-name="(record) => (isAbnormalRow(record) ? 'gf-abnormal-row' : '')"
|
||||||
|
row-key="name"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'abnormal'">
|
||||||
|
<Tag v-if="isAbnormalRow(record)" color="error" class="!m-0">异常</Tag>
|
||||||
|
<Tag v-else color="success" class="!m-0">正常</Tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'missing'">
|
||||||
|
<Tooltip
|
||||||
|
v-if="rowMissingDrugs(record).length"
|
||||||
|
:title="rowMissingDrugs(record).join('、')"
|
||||||
|
>
|
||||||
|
<span class="text-red-500">
|
||||||
|
{{ rowMissingDrugs(record).join('、') }}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
<Tag v-else color="success" class="!m-0">齐全</Tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
/* 异常处方行:浅红底,暗色下用半透明红 */
|
||||||
|
:deep(.gf-abnormal-row) > td {
|
||||||
|
background-color: hsl(var(--destructive) / 0.08) !important;
|
||||||
|
color: hsl(var(--destructive));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
/**
|
/**
|
||||||
* 金方新增/编辑弹窗
|
* 金方新增/编辑弹窗
|
||||||
|
* 打开时拉取金方来源下拉,通过 updateSchema 注入 source_id 选项
|
||||||
*/
|
*/
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
@@ -10,7 +11,11 @@ import { message } from 'ant-design-vue';
|
|||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
|
||||||
import { createGoldenFormula, updateGoldenFormula } from '../api';
|
import {
|
||||||
|
createGoldenFormula,
|
||||||
|
getGoldenFormulaSourceOption,
|
||||||
|
updateGoldenFormula,
|
||||||
|
} from '../api';
|
||||||
import { modalFormProps } from '../config/form';
|
import { modalFormProps } from '../config/form';
|
||||||
|
|
||||||
const isUpdate = ref(false);
|
const isUpdate = ref(false);
|
||||||
@@ -55,12 +60,28 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
modalApi.setState({ confirmLoading: false });
|
modalApi.setState({ confirmLoading: false });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onOpenChange(isOpen: boolean) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
|
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
formApi.resetForm();
|
formApi.resetForm();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 打开时拉取金方来源下拉选项并注入
|
||||||
|
try {
|
||||||
|
const list = await getGoldenFormulaSourceOption();
|
||||||
|
const options = (Array.isArray(list) ? list : []).map((item: any) => ({
|
||||||
|
label: String(item.name || ''),
|
||||||
|
value: Number(item.id || 0),
|
||||||
|
}));
|
||||||
|
formApi.updateSchema([
|
||||||
|
{
|
||||||
|
fieldName: 'source_id',
|
||||||
|
componentProps: { options },
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
} catch {
|
||||||
|
// 拉取失败不阻塞表单
|
||||||
|
}
|
||||||
const { values, update } = modalApi.getData<Record<string, any>>() || {};
|
const { values, update } = modalApi.getData<Record<string, any>>() || {};
|
||||||
isUpdate.value = !!update;
|
isUpdate.value = !!update;
|
||||||
if (values && update) {
|
if (values && update) {
|
||||||
@@ -76,7 +97,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
formApi.resetForm();
|
formApi.resetForm();
|
||||||
formApi.setValues({ sort: 0, status: 1, drugs_json: '' });
|
formApi.setValues({ sort: 0, status: 1, drugs_json: '', source_id: 0 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,10 +26,32 @@ export const modalFormProps: VbenFormProps = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'VbenInput',
|
component: 'VbenInput',
|
||||||
fieldName: 'source',
|
fieldName: 'clause_number',
|
||||||
|
label: '条文编号',
|
||||||
|
help: '典籍条文编号,如「第12条」,可空',
|
||||||
|
componentProps: { placeholder: '如:第12条' },
|
||||||
|
defaultValue: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenSelect',
|
||||||
|
fieldName: 'source_id',
|
||||||
label: '金方来源',
|
label: '金方来源',
|
||||||
|
help: '选择来源字典;如无合适项可先在「金方来源管理」新增',
|
||||||
|
componentProps: {
|
||||||
|
showSearch: true,
|
||||||
|
optionFilterProp: 'label',
|
||||||
|
options: [],
|
||||||
|
allowClear: true,
|
||||||
|
placeholder: '选择金方来源',
|
||||||
|
},
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenInput',
|
||||||
|
fieldName: 'source',
|
||||||
|
label: '来源(旧文本)',
|
||||||
formItemClass: 'col-span-6',
|
formItemClass: 'col-span-6',
|
||||||
componentProps: { placeholder: '如:伤寒论' },
|
componentProps: { placeholder: '如:伤寒论(旧字段,优先用上面的来源选择)' },
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
importGoldenFormula,
|
importGoldenFormula,
|
||||||
} from './api';
|
} from './api';
|
||||||
import FormModal from './components/modal.vue';
|
import FormModal from './components/modal.vue';
|
||||||
|
import ImportPreviewModal from './components/ImportPreviewModal.vue';
|
||||||
import { STATUS_OPTIONS } from './config/constants';
|
import { STATUS_OPTIONS } from './config/constants';
|
||||||
import { exportGoldenFormulaTemplate } from './utils/exportGoldenFormulaExcel';
|
import { exportGoldenFormulaTemplate } from './utils/exportGoldenFormulaExcel';
|
||||||
import { parseGoldenFormulaExcelBuffer } from './utils/parseGoldenFormulaExcel';
|
import { parseGoldenFormulaExcelBuffer } from './utils/parseGoldenFormulaExcel';
|
||||||
@@ -51,6 +52,18 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
label: '状态',
|
label: '状态',
|
||||||
componentProps: { allowClear: true, options: STATUS_OPTIONS },
|
componentProps: { allowClear: true, options: STATUS_OPTIONS },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenSelect',
|
||||||
|
fieldName: 'is_abnormal',
|
||||||
|
label: '异常标记',
|
||||||
|
componentProps: {
|
||||||
|
allowClear: true,
|
||||||
|
options: [
|
||||||
|
{ label: '正常', value: 0 },
|
||||||
|
{ label: '异常', value: 1 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
@@ -58,10 +71,27 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
columns: [
|
columns: [
|
||||||
{ type: 'checkbox', width: 60 },
|
{ type: 'checkbox', width: 60 },
|
||||||
{ field: 'id', title: 'ID', width: 80 },
|
{ field: 'id', title: 'ID', width: 80 },
|
||||||
{ field: 'name', title: '药方名字', minWidth: 140 },
|
{
|
||||||
{ field: 'source', title: '来源', width: 120 },
|
field: 'name',
|
||||||
|
title: '药方名字',
|
||||||
|
minWidth: 140,
|
||||||
|
slots: { default: 'name' },
|
||||||
|
},
|
||||||
|
{ field: 'clause_number', title: '条文编号', width: 110 },
|
||||||
|
{
|
||||||
|
field: 'source_name',
|
||||||
|
title: '来源',
|
||||||
|
width: 120,
|
||||||
|
slots: { default: 'source_name' },
|
||||||
|
},
|
||||||
{ field: 'herb_overview', title: '药材速览', minWidth: 180 },
|
{ field: 'herb_overview', title: '药材速览', minWidth: 180 },
|
||||||
{ field: 'drug_count', title: '药味数', width: 80 },
|
{ field: 'drug_count', title: '药味数', width: 80 },
|
||||||
|
{
|
||||||
|
field: 'is_abnormal',
|
||||||
|
title: '异常',
|
||||||
|
width: 90,
|
||||||
|
slots: { default: 'is_abnormal' },
|
||||||
|
},
|
||||||
{ field: 'pinyin_initials', title: '首拼', width: 100 },
|
{ field: 'pinyin_initials', title: '首拼', width: 100 },
|
||||||
{ field: 'status', title: '状态', width: 90, slots: { default: 'status' } },
|
{ field: 'status', title: '状态', width: 90, slots: { default: 'status' } },
|
||||||
{ field: 'sort', title: '排序', width: 80 },
|
{ field: 'sort', title: '排序', width: 80 },
|
||||||
@@ -104,6 +134,11 @@ const [FormModalComp, formModalApi] = useVbenModal({
|
|||||||
connectedComponent: FormModal,
|
connectedComponent: FormModal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 导入预览弹窗:解析后预览 + 选金方来源 + 确认导入 */
|
||||||
|
const [ImportPreviewModalComp, importPreviewApi] = useVbenModal({
|
||||||
|
connectedComponent: ImportPreviewModal,
|
||||||
|
});
|
||||||
|
|
||||||
const showModal = (data: any = {}, isUpdate = false) => {
|
const showModal = (data: any = {}, isUpdate = false) => {
|
||||||
formModalApi.setData({
|
formModalApi.setData({
|
||||||
values: data,
|
values: data,
|
||||||
@@ -144,7 +179,27 @@ async function handleImportFile(file: File) {
|
|||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const res = await importGoldenFormula({ rows: parsed.items });
|
// 打开预览弹窗:用户选金方来源 + 确认后再提交
|
||||||
|
importPreviewApi.setData({ rows: parsed.items });
|
||||||
|
importPreviewApi.open();
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e?.message || '解析失败');
|
||||||
|
} finally {
|
||||||
|
importing.value = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览弹窗确认后:带 source_id 提交导入,弹结果框
|
||||||
|
*/
|
||||||
|
async function handleConfirmImport(payload: { rows: any[]; sourceId: number }) {
|
||||||
|
importing.value = true;
|
||||||
|
try {
|
||||||
|
const res = await importGoldenFormula({
|
||||||
|
rows: payload.rows,
|
||||||
|
source_id: payload.sourceId,
|
||||||
|
});
|
||||||
const ok = Number(res?.success || 0);
|
const ok = Number(res?.success || 0);
|
||||||
const fail = Number(res?.fail || 0);
|
const fail = Number(res?.fail || 0);
|
||||||
const errors = Array.isArray(res?.errors) ? res.errors : [];
|
const errors = Array.isArray(res?.errors) ? res.errors : [];
|
||||||
@@ -161,12 +216,12 @@ async function handleImportFile(file: File) {
|
|||||||
} finally {
|
} finally {
|
||||||
importing.value = false;
|
importing.value = false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height title="金方管理">
|
<Page auto-content-height title="金方管理">
|
||||||
<FormModalComp />
|
<FormModalComp />
|
||||||
|
<ImportPreviewModalComp @confirm="handleConfirmImport" />
|
||||||
<Grid>
|
<Grid>
|
||||||
<template #toolbar-buttons>
|
<template #toolbar-buttons>
|
||||||
<TableAction
|
<TableAction
|
||||||
@@ -203,6 +258,19 @@ async function handleImportFile(file: File) {
|
|||||||
<Button :loading="importing" type="default">导入 Excel</Button>
|
<Button :loading="importing" type="default">导入 Excel</Button>
|
||||||
</Upload>
|
</Upload>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 异常处方:药方名字整列标红 -->
|
||||||
|
<template #name="{ row }">
|
||||||
|
<span :class="{ 'gf-name-abnormal': row.is_abnormal === 1 }">
|
||||||
|
{{ row.name }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #source_name="{ row }">
|
||||||
|
{{ row.source_name || row.source || '—' }}
|
||||||
|
</template>
|
||||||
|
<template #is_abnormal="{ row }">
|
||||||
|
<Tag v-if="row.is_abnormal === 1" color="error">异常</Tag>
|
||||||
|
<Tag v-else color="success">正常</Tag>
|
||||||
|
</template>
|
||||||
<template #status="{ row }">
|
<template #status="{ row }">
|
||||||
<Tag :color="row.status === 1 ? 'success' : 'default'">
|
<Tag :color="row.status === 1 ? 'success' : 'default'">
|
||||||
{{ row.status === 1 ? '启用' : '禁用' }}
|
{{ row.status === 1 ? '启用' : '禁用' }}
|
||||||
@@ -229,3 +297,10 @@ async function handleImportFile(file: File) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
/* 异常处方药方名字:用主题 destructive 色,暗色下可读 */
|
||||||
|
.gf-name-abnormal {
|
||||||
|
color: hsl(var(--destructive));
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -8,17 +8,16 @@ const THIN_BORDER: Partial<ExcelJS.Borders> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const HEADERS = [
|
const HEADERS = [
|
||||||
|
'序号',
|
||||||
'药方名字',
|
'药方名字',
|
||||||
|
'条文编号',
|
||||||
'药品JSON',
|
'药品JSON',
|
||||||
'药材速览',
|
'药材速览',
|
||||||
'原方',
|
'原方',
|
||||||
'药方主治(原文)',
|
'药方主治(文言文)',
|
||||||
'药方主治(译文)',
|
'现代主治(白话文)',
|
||||||
'简介',
|
'拼音首拼',
|
||||||
'封面',
|
'拼音全拼',
|
||||||
'药方拼音首拼',
|
|
||||||
'药方拼音全拼',
|
|
||||||
'金方来源',
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,17 +27,16 @@ export async function exportGoldenFormulaTemplate(): Promise<ArrayBuffer> {
|
|||||||
const workbook = new ExcelJS.Workbook();
|
const workbook = new ExcelJS.Workbook();
|
||||||
const sheet = workbook.addWorksheet('金方导入');
|
const sheet = workbook.addWorksheet('金方导入');
|
||||||
sheet.columns = [
|
sheet.columns = [
|
||||||
{ width: 14 },
|
{ width: 8 }, // 序号
|
||||||
{ width: 52 },
|
{ width: 14 }, // 药方名字
|
||||||
{ width: 24 },
|
{ width: 14 }, // 条文编号
|
||||||
{ width: 48 },
|
{ width: 52 }, // 药品JSON
|
||||||
{ width: 24 },
|
{ width: 24 }, // 药材速览
|
||||||
{ width: 24 },
|
{ width: 48 }, // 原方
|
||||||
{ width: 20 },
|
{ width: 28 }, // 药方主治(文言文)
|
||||||
{ width: 20 },
|
{ width: 28 }, // 现代主治(白话文)
|
||||||
{ width: 12 },
|
{ width: 12 }, // 拼音首拼
|
||||||
{ width: 16 },
|
{ width: 16 }, // 拼音全拼
|
||||||
{ width: 12 },
|
|
||||||
];
|
];
|
||||||
const headerRow = sheet.addRow([...HEADERS]);
|
const headerRow = sheet.addRow([...HEADERS]);
|
||||||
headerRow.height = 28;
|
headerRow.height = 28;
|
||||||
@@ -58,17 +56,16 @@ export async function exportGoldenFormulaTemplate(): Promise<ArrayBuffer> {
|
|||||||
const sampleOriginal =
|
const sampleOriginal =
|
||||||
'桂枝三两,去皮 芍药三两 甘草二两,炙 生姜三两,切 大枣十二枚,擘。右五味,㕮咀三味,以水七升,微火煮取三升,去滓,适寒温,服一升。服已须臾,啜热稀粥一升余,以助药力。禁生冷、粘滑、肉面、五辛、酒酪、臭恶等物。';
|
'桂枝三两,去皮 芍药三两 甘草二两,炙 生姜三两,切 大枣十二枚,擘。右五味,㕮咀三味,以水七升,微火煮取三升,去滓,适寒温,服一升。服已须臾,啜热稀粥一升余,以助药力。禁生冷、粘滑、肉面、五辛、酒酪、臭恶等物。';
|
||||||
const sample = sheet.addRow([
|
const sample = sheet.addRow([
|
||||||
'桂枝汤',
|
1, // 序号
|
||||||
sampleDrugs,
|
'桂枝汤', // 药方名字
|
||||||
'桂枝、芍药、甘草、生姜、大枣',
|
'第12条', // 条文编号
|
||||||
sampleOriginal,
|
sampleDrugs, // 药品JSON
|
||||||
'太阳中风,阳浮而阴弱',
|
'桂枝、芍药、甘草、生姜、大枣', // 药材速览
|
||||||
'外感风寒表虚证',
|
sampleOriginal, // 原方
|
||||||
'解肌发表,调和营卫',
|
'太阳中风,阳浮而阴弱', // 药方主治(文言文)
|
||||||
'',
|
'外感风寒表虚证', // 现代主治(白话文)
|
||||||
'gzt',
|
'gzt', // 拼音首拼
|
||||||
'guizhitang',
|
'guizhitang', // 拼音全拼
|
||||||
'伤寒论',
|
|
||||||
]);
|
]);
|
||||||
sample.height = 80;
|
sample.height = 80;
|
||||||
sample.eachCell((cell) => {
|
sample.eachCell((cell) => {
|
||||||
@@ -90,7 +87,9 @@ export async function exportGoldenFormulaTemplate(): Promise<ArrayBuffer> {
|
|||||||
cell.border = THIN_BORDER;
|
cell.border = THIN_BORDER;
|
||||||
});
|
});
|
||||||
const tips: [string, string][] = [
|
const tips: [string, string][] = [
|
||||||
|
['序号', '仅作行号,不导入;可空'],
|
||||||
['药方名字', '必填'],
|
['药方名字', '必填'],
|
||||||
|
['条文编号', '典籍条文编号,如「第12条」,可空'],
|
||||||
[
|
[
|
||||||
'药品JSON',
|
'药品JSON',
|
||||||
'系统字段(导入用):name/dose(克数数字)/unit(固定g)/usage(先煎、后下等,可空)。展示扩展(仅展示):ancient_dose(如三两、十二枚)、prep(去皮、炙、切、擘)。勿把古代单位或炮制写进 dose/unit/usage。',
|
'系统字段(导入用):name/dose(克数数字)/unit(固定g)/usage(先煎、后下等,可空)。展示扩展(仅展示):ancient_dose(如三两、十二枚)、prep(去皮、炙、切、擘)。勿把古代单位或炮制写进 dose/unit/usage。',
|
||||||
@@ -100,13 +99,10 @@ export async function exportGoldenFormulaTemplate(): Promise<ArrayBuffer> {
|
|||||||
'原方',
|
'原方',
|
||||||
'典籍药味+煮法+服法+禁忌全文,如「桂枝三两,去皮…服一升…禁生冷…」',
|
'典籍药味+煮法+服法+禁忌全文,如「桂枝三两,去皮…服一升…禁生冷…」',
|
||||||
],
|
],
|
||||||
['药方主治(原文)', '主治典籍原文'],
|
['药方主治(文言文)', '主治典籍原文'],
|
||||||
['药方主治(译文)', '主治白话译文'],
|
['现代主治(白话文)', '主治白话译文'],
|
||||||
['简介', '方义/说明'],
|
['拼音首拼', '可空,系统按药方名自动生成'],
|
||||||
['封面', '封面图完整 URL,可空'],
|
['拼音全拼', '可空,系统按药方名自动生成'],
|
||||||
['药方拼音首拼', '可空,系统按药方名自动生成'],
|
|
||||||
['药方拼音全拼', '可空,系统按药方名自动生成'],
|
|
||||||
['金方来源', '如伤寒论、金匮要略'],
|
|
||||||
];
|
];
|
||||||
tips.forEach(([k, v]) => {
|
tips.forEach(([k, v]) => {
|
||||||
const row = tip.addRow([k, v]);
|
const row = tip.addRow([k, v]);
|
||||||
|
|||||||
@@ -4,16 +4,14 @@ const MAX_ROWS = 2000;
|
|||||||
|
|
||||||
export type ParsedGoldenFormulaRow = {
|
export type ParsedGoldenFormulaRow = {
|
||||||
name: string;
|
name: string;
|
||||||
|
clause_number: string;
|
||||||
drugs_json: string;
|
drugs_json: string;
|
||||||
herb_overview: string;
|
herb_overview: string;
|
||||||
original_formula: string;
|
original_formula: string;
|
||||||
indication_original: string;
|
indication_original: string;
|
||||||
indication_translation: string;
|
indication_translation: string;
|
||||||
intro: string;
|
|
||||||
cover: string;
|
|
||||||
pinyin_initials: string;
|
pinyin_initials: string;
|
||||||
pinyin: string;
|
pinyin: string;
|
||||||
source: string;
|
|
||||||
status: number;
|
status: number;
|
||||||
sort: number;
|
sort: number;
|
||||||
};
|
};
|
||||||
@@ -51,16 +49,31 @@ function normalizeHeader(value: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NAME_ALIASES = ['药方名字', '药方名称', '方名', 'name'];
|
const NAME_ALIASES = ['药方名字', '药方名称', '方名', 'name'];
|
||||||
|
const CLAUSE_ALIASES = ['条文编号', '条文', '条款', 'clause_number'];
|
||||||
const DRUGS_ALIASES = ['药品JSON', '药品json', '药品', 'drugs_json', 'drugs'];
|
const DRUGS_ALIASES = ['药品JSON', '药品json', '药品', 'drugs_json', 'drugs'];
|
||||||
const HERB_ALIASES = ['药材速览', '速览', 'herb_overview'];
|
const HERB_ALIASES = ['药材速览', '速览', 'herb_overview'];
|
||||||
const ORIGINAL_ALIASES = ['原方', '原方全文', 'original_formula', '方文'];
|
const ORIGINAL_ALIASES = ['原方', '原方全文', 'original_formula', '方文'];
|
||||||
const IND_ORI_ALIASES = ['药方主治(原文)', '药方主治原文', '主治原文', 'indication_original'];
|
const IND_ORI_ALIASES = [
|
||||||
const IND_TR_ALIASES = ['药方主治(译文)', '药方主治译文', '主治译文', 'indication_translation'];
|
'药方主治(文言文)',
|
||||||
const INTRO_ALIASES = ['简介', 'intro'];
|
'药方主治(文言文)',
|
||||||
const COVER_ALIASES = ['封面', '封面图', 'cover'];
|
'药方主治原文',
|
||||||
const PY_ABBR_ALIASES = ['药方拼音首拼', '拼音首拼', '首拼', 'pinyin_initials'];
|
'主治文言文',
|
||||||
const PY_FULL_ALIASES = ['药方拼音全拼', '拼音全拼', '全拼', 'pinyin'];
|
'文言文',
|
||||||
const SOURCE_ALIASES = ['金方来源', '来源', 'source'];
|
'indication_original',
|
||||||
|
];
|
||||||
|
const IND_TR_ALIASES = [
|
||||||
|
'现代主治(白话文)',
|
||||||
|
'现代主治(白话文)',
|
||||||
|
'现代主治',
|
||||||
|
'白话文',
|
||||||
|
'药方主治(译文)',
|
||||||
|
'药方主治(译文)',
|
||||||
|
'药方主治译文',
|
||||||
|
'主治译文',
|
||||||
|
'indication_translation',
|
||||||
|
];
|
||||||
|
const PY_ABBR_ALIASES = ['拼音首拼', '药方拼音首拼', '首拼', 'pinyin_initials'];
|
||||||
|
const PY_FULL_ALIASES = ['拼音全拼', '药方拼音全拼', '全拼', 'pinyin'];
|
||||||
const STATUS_ALIASES = ['状态', 'status'];
|
const STATUS_ALIASES = ['状态', 'status'];
|
||||||
const SORT_ALIASES = ['排序', 'sort'];
|
const SORT_ALIASES = ['排序', 'sort'];
|
||||||
|
|
||||||
@@ -129,16 +142,14 @@ export async function parseGoldenFormulaExcelBuffer(
|
|||||||
if (!nameCol) {
|
if (!nameCol) {
|
||||||
throw new Error('模板表头缺少「药方名字」列,请重新下载模板');
|
throw new Error('模板表头缺少「药方名字」列,请重新下载模板');
|
||||||
}
|
}
|
||||||
|
const clauseCol = pickColumn(headerMap, CLAUSE_ALIASES);
|
||||||
const drugsCol = pickColumn(headerMap, DRUGS_ALIASES);
|
const drugsCol = pickColumn(headerMap, DRUGS_ALIASES);
|
||||||
const herbCol = pickColumn(headerMap, HERB_ALIASES);
|
const herbCol = pickColumn(headerMap, HERB_ALIASES);
|
||||||
const originalCol = pickColumn(headerMap, ORIGINAL_ALIASES);
|
const originalCol = pickColumn(headerMap, ORIGINAL_ALIASES);
|
||||||
const indOriCol = pickColumn(headerMap, IND_ORI_ALIASES);
|
const indOriCol = pickColumn(headerMap, IND_ORI_ALIASES);
|
||||||
const indTrCol = pickColumn(headerMap, IND_TR_ALIASES);
|
const indTrCol = pickColumn(headerMap, IND_TR_ALIASES);
|
||||||
const introCol = pickColumn(headerMap, INTRO_ALIASES);
|
|
||||||
const coverCol = pickColumn(headerMap, COVER_ALIASES);
|
|
||||||
const pyAbbrCol = pickColumn(headerMap, PY_ABBR_ALIASES);
|
const pyAbbrCol = pickColumn(headerMap, PY_ABBR_ALIASES);
|
||||||
const pyFullCol = pickColumn(headerMap, PY_FULL_ALIASES);
|
const pyFullCol = pickColumn(headerMap, PY_FULL_ALIASES);
|
||||||
const sourceCol = pickColumn(headerMap, SOURCE_ALIASES);
|
|
||||||
const statusCol = pickColumn(headerMap, STATUS_ALIASES);
|
const statusCol = pickColumn(headerMap, STATUS_ALIASES);
|
||||||
const sortCol = pickColumn(headerMap, SORT_ALIASES);
|
const sortCol = pickColumn(headerMap, SORT_ALIASES);
|
||||||
const items: ParsedGoldenFormulaRow[] = [];
|
const items: ParsedGoldenFormulaRow[] = [];
|
||||||
@@ -160,16 +171,14 @@ export async function parseGoldenFormulaExcelBuffer(
|
|||||||
const statusRaw = getCellInt(row, statusCol, 1);
|
const statusRaw = getCellInt(row, statusCol, 1);
|
||||||
items.push({
|
items.push({
|
||||||
name,
|
name,
|
||||||
|
clause_number: getCellText(row, clauseCol),
|
||||||
drugs_json: getCellText(row, drugsCol),
|
drugs_json: getCellText(row, drugsCol),
|
||||||
herb_overview: getCellText(row, herbCol),
|
herb_overview: getCellText(row, herbCol),
|
||||||
original_formula: getCellText(row, originalCol),
|
original_formula: getCellText(row, originalCol),
|
||||||
indication_original: getCellText(row, indOriCol),
|
indication_original: getCellText(row, indOriCol),
|
||||||
indication_translation: getCellText(row, indTrCol),
|
indication_translation: getCellText(row, indTrCol),
|
||||||
intro: getCellText(row, introCol),
|
|
||||||
cover: getCellText(row, coverCol),
|
|
||||||
pinyin_initials: getCellText(row, pyAbbrCol),
|
pinyin_initials: getCellText(row, pyAbbrCol),
|
||||||
pinyin: getCellText(row, pyFullCol),
|
pinyin: getCellText(row, pyFullCol),
|
||||||
source: getCellText(row, sourceCol),
|
|
||||||
status: statusRaw === 0 ? 0 : 1,
|
status: statusRaw === 0 ? 0 : 1,
|
||||||
sort: getCellInt(row, sortCol, 0),
|
sort: getCellInt(row, sortCol, 0),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user