批量修改药品编号
Some checks failed
Close stale issues / stale (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CI / CI OK (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled

This commit is contained in:
李琦
2026-03-25 08:55:39 +08:00
parent 8bcae19369
commit 6a8cc0458d
8 changed files with 1230 additions and 125 deletions

View File

@@ -47,6 +47,7 @@
"markdown-it": "^14.1.0",
"pinia": "catalog:",
"vue": "catalog:",
"vue-router": "catalog:"
"vue-router": "catalog:",
"xlsx": "^0.18.5"
}
}

View File

@@ -1,5 +1,3 @@
import type { RequestResponse } from '@vben/request';
import { requestClient } from '#/api/request';
const prefix = 'china-medicine/';
@@ -66,3 +64,35 @@ export async function deleteChinaMedicine(data: Record<string, any>) {
export async function importChinaMedicineApi(data: Record<string, any>) {
return requestClient.upload(`${prefix}import`, data);
}
/**
* 预览批量更新药品编码(只读)
*/
export async function previewBatchDrugNumberApi(data: {
items: { drug_name: string; drug_number: string }[];
}) {
return requestClient.post<any>(`${prefix}preview-batch-drug-number`, data);
}
/**
* 批量更新编码:全量中药 id/名称/编号(卡片参考)
*/
export async function getAllDrugNumbersForBatchApi() {
return requestClient.get<{
items: {
id: number;
drug_name: string;
drug_number: null | string;
pinyin_simple: string;
}[];
}>(`${prefix}list-all-drug-numbers-for-batch`);
}
/**
* 确认批量更新药品编码
*/
export async function applyBatchDrugNumberApi(data: {
items: { id: number; drug_number: string }[];
}) {
return requestClient.post<any>(`${prefix}apply-batch-drug-number`, data);
}

View File

@@ -0,0 +1,485 @@
<script lang="ts" setup>
import type { UploadFile } from 'ant-design-vue';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import {
Button,
Card,
Input,
message,
Modal,
Segmented,
Spin,
Table,
Tag,
UploadDragger,
} from 'ant-design-vue';
import {
applyBatchDrugNumberApi,
getAllDrugNumbersForBatchApi,
previewBatchDrugNumberApi,
} from '../api';
import {
isDrugNumberChanged,
parseDrugNumberExcelBuffer,
} from '../utils/parseDrugNumberExcel';
type MatchedRow = {
id: number;
drug_name: string;
drug_number_old: string | null;
drug_number_new: string;
};
type MatchedRowView = MatchedRow & { _changed: boolean };
type AllDrugItem = {
id: number;
drug_name: string;
drug_number: null | string;
pinyin_simple?: string | null;
};
type CardStatusFilter = 'all' | 'changed' | 'unchanged';
const gridApi = ref();
const fileList = ref<UploadFile[]>([]);
const selectedFile = ref<File | null>(null);
const matched = ref<MatchedRow[]>([]);
const notFound = ref<{ drug_name: string; drug_number: string }[]>([]);
const invalidRows = ref<{ row: number; drug_name: string; drug_number: string }[]>(
[],
);
const previewLoading = ref(false);
const applyLoading = ref(false);
const parseHint = ref('');
const allDrugs = ref<AllDrugItem[]>([]);
/** 打开弹窗时服务端原始编号,用于判断是否有变更 */
const initialNumbers = ref<Record<number, string>>({});
/** 当前编辑中的编号(卡片可改;导入解析会写入) */
const localNumbers = ref<Record<number, string>>({});
const loadingAll = ref(false);
const cardFilter = ref('');
const cardStatusFilter = ref<CardStatusFilter>('all');
const matchedView = computed<MatchedRowView[]>(() =>
matched.value.map((row) => {
const newNum =
localNumbers.value[row.id] ?? String(row.drug_number_new ?? '');
return {
...row,
drug_number_new: newNum,
_changed: isDrugNumberChanged(initialNumbers.value[row.id], newNum),
};
}),
);
const changedCount = computed(
() => matchedView.value.filter((r) => r._changed).length,
);
const cardStatusCounts = computed(() => {
const all = allDrugs.value.length;
const changed = allDrugs.value.filter((d) => cardIsChanged(d)).length;
return {
all,
changed,
unchanged: all - changed,
};
});
/** 含卡片直接编辑:任意与初始值不同的条数 */
const totalPendingChanges = computed(() =>
allDrugs.value.filter((d) =>
isDrugNumberChanged(
initialNumbers.value[d.id],
localNumbers.value[d.id],
),
).length,
);
function cardIsChanged(d: AllDrugItem) {
return isDrugNumberChanged(
initialNumbers.value[d.id],
localNumbers.value[d.id],
);
}
function setLocalNumber(id: number, v: string) {
localNumbers.value = { ...localNumbers.value, [id]: v ?? '' };
}
const filteredCards = computed(() => {
const q = cardFilter.value.trim().toLowerCase();
const keywordFiltered = allDrugs.value.filter((d) => {
const nameOk = d.drug_name.toLowerCase().includes(q);
const py = (d.pinyin_simple ?? '').toLowerCase();
const pyOk = py.includes(q);
return q ? nameOk || pyOk : true;
});
if (cardStatusFilter.value === 'changed') {
return keywordFiltered.filter((d) => cardIsChanged(d));
}
if (cardStatusFilter.value === 'unchanged') {
return keywordFiltered.filter((d) => !cardIsChanged(d));
}
return keywordFiltered;
});
const columns = [
{ title: 'ID', dataIndex: 'id', key: 'id', width: 72 },
{ title: '药品名称', dataIndex: 'drug_name', key: 'drug_name', ellipsis: true },
{
title: '当前编号',
dataIndex: 'drug_number_old',
key: 'drug_number_old',
width: 120,
},
{
title: '新编号',
dataIndex: 'drug_number_new',
key: 'drug_number_new',
width: 120,
},
{
title: '状态',
dataIndex: '_changed',
key: 'status',
width: 88,
},
];
function resetState() {
selectedFile.value = null;
fileList.value = [];
matched.value = [];
notFound.value = [];
invalidRows.value = [];
parseHint.value = '';
cardFilter.value = '';
cardStatusFilter.value = 'all';
allDrugs.value = [];
initialNumbers.value = {};
localNumbers.value = {};
}
const [ModalComp, modalApi] = useVbenModal({
fullscreenButton: false,
draggable: true,
showConfirmButton: false,
class: '!w-[min(960px,92vw)]',
onCancel() {
resetState();
modalApi.close();
},
onOpenChange(isOpen: boolean) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
if (isOpen) {
loadAllDrugs();
} else {
resetState();
}
},
});
async function loadAllDrugs() {
loadingAll.value = true;
try {
const res = await getAllDrugNumbersForBatchApi();
const items = res?.items ?? [];
allDrugs.value = items;
const init: Record<number, string> = {};
const loc: Record<number, string> = {};
for (const d of items) {
const s = d.drug_number ?? '';
init[d.id] = s;
loc[d.id] = s;
}
initialNumbers.value = init;
localNumbers.value = loc;
} catch {
message.error('加载中药列表失败');
allDrugs.value = [];
initialNumbers.value = {};
localNumbers.value = {};
} finally {
loadingAll.value = false;
}
}
const handleFileChange = (info: { file: UploadFile }) => {
const { file } = info;
if (file.status === 'removed') {
selectedFile.value = null;
matched.value = [];
notFound.value = [];
invalidRows.value = [];
parseHint.value = '';
return;
}
const raw = file.originFileObj ?? file;
if (raw instanceof File) {
selectedFile.value = raw;
}
};
function rowClassName(record: MatchedRowView) {
return record._changed ? 'drug-batch-row-changed' : 'drug-batch-row-unchanged';
}
async function runPreview() {
if (!selectedFile.value) {
message.warning('请先选择 Excel 文件');
return;
}
previewLoading.value = true;
try {
const buf = await selectedFile.value.arrayBuffer();
const { items, invalidCount, rowCount } = parseDrugNumberExcelBuffer(buf);
if (items.length === 0) {
message.warning('未解析到有效的「药品编号」与「药品名称」行');
matched.value = [];
notFound.value = [];
invalidRows.value = [];
parseHint.value = '';
return;
}
parseHint.value =
rowCount > 5000
? `${rowCount} 行,已按上限处理前 5000 行。`
: `共解析 ${items.length} 条有效行。`;
const res = await previewBatchDrugNumberApi({ items });
matched.value = (res.matched || []) as MatchedRow[];
notFound.value = res.not_found || [];
invalidRows.value = res.invalid || [];
for (const m of matched.value) {
const v = String(m.drug_number_new ?? '');
localNumbers.value = { ...localNumbers.value, [m.id]: v };
}
if (invalidCount > 0) {
message.info(`Excel 中有 ${invalidCount} 行因缺少编号或名称被跳过`);
}
if ((notFound.value?.length || 0) > 0) {
Modal.warning({
title: '部分药品名称在系统中未找到',
content: `未匹配 ${notFound.value.length} 条,请核对药名是否与后台完全一致。`,
});
}
} catch {
message.error('预览失败');
} finally {
previewLoading.value = false;
}
}
async function runApply() {
const items = allDrugs.value
.filter((d) =>
isDrugNumberChanged(
initialNumbers.value[d.id],
localNumbers.value[d.id],
),
)
.map((d) => ({
id: d.id,
drug_number: String(localNumbers.value[d.id] ?? ''),
}));
if (items.length === 0) {
message.info('没有需要更新的编号(与打开弹窗时一致)');
return;
}
applyLoading.value = true;
modalApi.setState({ loading: true });
try {
const res = await applyBatchDrugNumberApi({ items });
const n = res?.updated_count ?? res?.updatedCount ?? 0;
message.success(`已更新 ${n} 条记录`);
gridApi.value?.reload();
resetState();
modalApi.close();
} catch {
message.error('更新失败');
} finally {
applyLoading.value = false;
modalApi.setState({ loading: false });
}
}
</script>
<template>
<ModalComp title="批量更新药品编码">
<div class="mb-3 text-sm text-gray-600 dark:text-gray-300">
下方卡片可直接修改编号亦可使用 Excel首列表头为药品编号/药品编码第二列药品名称或与之一致的列顺序仅更新编号与更新时间
</div>
<Spin :spinning="loadingAll">
<div class="mb-1 text-sm font-medium text-gray-800 dark:text-gray-100">
当前中药参考
<Tag>{{ filteredCards.length }}</Tag>
</div>
<Input
v-model:value="cardFilter"
allow-clear
class="mb-2"
placeholder="按药品名称或拼音首拼筛选"
/>
<Segmented
v-model:value="cardStatusFilter"
class="mb-2"
:options="[
{ label: `全部 ${cardStatusCounts.all}`, value: 'all' },
{ label: `有变动 ${cardStatusCounts.changed}`, value: 'changed' },
{
label: `无变动 ${cardStatusCounts.unchanged}`,
value: 'unchanged',
},
]"
/>
<div
class="mb-4 grid max-h-56 grid-cols-2 gap-2 overflow-y-auto sm:grid-cols-3 md:grid-cols-4"
>
<Card
v-for="d in filteredCards"
:key="d.id"
:class="[
'!mb-0 shadow-sm transition-colors',
cardIsChanged(d)
? '!border-amber-400 bg-amber-50 dark:!border-amber-500/70 dark:bg-amber-950/45'
: '',
]"
size="small"
>
<div
class="truncate text-sm font-medium text-gray-900 dark:text-gray-100"
:title="d.drug_name"
>
{{ d.drug_name }}
</div>
<Input
class="mt-1"
placeholder="药品编号"
size="small"
:value="localNumbers[d.id] ?? ''"
@update:value="(v: string) => setLocalNumber(d.id, v)"
/>
</Card>
</div>
</Spin>
<UploadDragger
v-model:file-list="fileList"
:before-upload="() => false"
:max-count="1"
accept=".xlsx,.xls"
name="file"
@change="handleFileChange"
>
<p class="ant-upload-text">点击或拖拽选择 Excel</p>
<p class="ant-upload-hint">选择后点击「导入并解析」从服务器比对当前编号</p>
</UploadDragger>
<div class="mt-3 flex flex-wrap gap-2">
<Button
:loading="previewLoading"
type="primary"
@click="runPreview"
>
导入并解析
</Button>
<Button
:disabled="totalPendingChanges === 0"
:loading="applyLoading"
danger
type="primary"
@click="runApply"
>
确认更新到数据库
</Button>
</div>
<p v-if="parseHint" class="mt-2 text-xs text-gray-500 dark:text-gray-400">
{{ parseHint }}
</p>
<div class="mt-2 text-sm text-gray-600 dark:text-gray-300">
待保存变更:
<Tag :color="totalPendingChanges > 0 ? 'orange' : 'default'">{{
totalPendingChanges
}}</Tag>
条(含卡片修改与导入)
</div>
<div v-if="matched.length > 0" class="mt-4">
<div class="mb-2 text-sm font-medium text-gray-900 dark:text-gray-100">
本次 Excel 匹配
<Tag color="blue">{{ matched.length }}</Tag>
条,其中
<Tag v-if="changedCount > 0" color="orange">{{ changedCount }}</Tag>
<Tag v-else color="default">0</Tag>
条与初始编号不同
</div>
<Table
:columns="columns"
:data-source="matchedView"
:pagination="{ pageSize: 8 }"
:row-class-name="(record: MatchedRowView) => rowClassName(record)"
:row-key="(row: MatchedRowView, index: number) => `${row.id}-${index}`"
:scroll="{ y: 280 }"
size="small"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<Tag v-if="record._changed" color="orange">已变更</Tag>
<Tag v-else color="default">无变化</Tag>
</template>
</template>
</Table>
</div>
<div v-if="notFound.length > 0" class="mt-4">
<div class="mb-1 text-sm font-medium text-orange-600 dark:text-orange-400">
未找到对应中药({{ notFound.length }}
</div>
<div
class="max-h-32 overflow-auto rounded border border-orange-100 bg-orange-50 p-2 text-xs text-gray-800 dark:border-orange-900/60 dark:bg-orange-950/40 dark:text-gray-200"
>
<div v-for="(row, i) in notFound.slice(0, 50)" :key="i">
{{ row.drug_name }} — {{ row.drug_number }}
</div>
<div v-if="notFound.length > 50">… 其余 {{ notFound.length - 50 }} 条略</div>
</div>
</div>
<div
v-if="invalidRows.length > 0"
class="mt-2 text-xs text-gray-500 dark:text-gray-400"
>
服务端标记无效行{{ invalidRows.length }}
</div>
</ModalComp>
</template>
<style scoped>
:deep(.drug-batch-row-changed) > td {
background-color: rgb(254 252 232) !important;
}
:deep(.dark .drug-batch-row-changed) > td {
background-color: rgb(69 26 3 / 0.45) !important;
}
:deep(.drug-batch-row-unchanged) > td {
opacity: 0.85;
}
:deep(.dark .drug-batch-row-unchanged) > td {
opacity: 0.92;
}
</style>

View File

@@ -25,6 +25,7 @@ export const gridOptions: VxeGridProps<RowType> = {
{ type: 'checkbox', width: 60 },
{ field: 'id', align: 'left', title: 'ID', width: 100 },
{ field: 'drug_name', align: 'left', title: '药品名称' },
{ field: 'drug_number', align: 'left', title: '药品编码erp' },
{ field: 'pinyin_simple', title: '拼音' },
{ field: 'status', title: '状态', slots: { default: 'status' } },
{ field: 'created_at', title: '发布时间' },

View File

@@ -12,6 +12,7 @@ import { TableAction } from '#/components/table-action';
import { downloadByData } from '#/util/tool';
import { deleteChinaMedicine, exportChinaMedicineApi } from './api';
import DrugNumberBatchModal from './components/DrugNumberBatchModal.vue';
import FormModalDemo from './components/modal.vue';
import ExcelUpload from './components/ExcelUpload.vue';
import { formOptions } from './config/search';
@@ -46,6 +47,10 @@ const [ExcelUploadModal, ExcelUploadModalApi] = useVbenModal({
connectedComponent: ExcelUpload,
});
const [DrugNumberBatchModalComp, drugNumberBatchModalApi] = useVbenModal({
connectedComponent: DrugNumberBatchModal,
});
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
@@ -86,11 +91,19 @@ const openExcelUploadModal = () => {
});
ExcelUploadModalApi.open();
};
const openDrugNumberBatchModal = () => {
drugNumberBatchModalApi.setData({
gridApi,
});
drugNumberBatchModalApi.open();
};
</script>
<template>
<Page auto-content-height title="中药管理">
<ExcelUploadModal />
<DrugNumberBatchModalComp />
<FormModal />
<Grid>
<template #toolbar-buttons>
@@ -110,6 +123,12 @@ const openExcelUploadModal = () => {
// auth: ['超级中药', 'sys:user:save'],
onClick: openExcelUploadModal.bind(null),
},
{
label: '批量更新编码',
type: 'default',
icon: 'ant-design:barcode-outlined',
onClick: openDrugNumberBatchModal.bind(null),
},
{
label: '导出',
type: 'primary',

View File

@@ -0,0 +1,214 @@
import * as XLSX from 'xlsx';
const MAX_ROWS = 5000;
export type ParsedDrugNumberRow = {
drug_name: string;
drug_number: string;
};
function normalizeCell(value: unknown): string {
if (value === null || value === undefined) {
return '';
}
if (typeof value === 'number') {
return Number.isInteger(value) ? String(value) : String(value).trim();
}
return String(value).trim();
}
/** 表头含空白或全角空格时仍能匹配 */
function pickByHeaderAliases(
row: Record<string, unknown>,
aliases: string[],
): string {
for (const key of Object.keys(row)) {
const k = key.replace(/\s+/g, '').replace(/\u3000/g, '');
for (const alias of aliases) {
if (k === alias.replace(/\s+/g, '').replace(/\u3000/g, '')) {
return normalizeCell(row[key]);
}
}
}
return '';
}
function parseRowsByObjectKeys(
rows: Record<string, unknown>[],
): { items: ParsedDrugNumberRow[]; invalidCount: number } {
const items: ParsedDrugNumberRow[] = [];
let invalidCount = 0;
const n = Math.min(rows.length, MAX_ROWS);
const numberAliases = [
'药品编号',
'药品编码',
'编号',
'drug_number',
'编码',
];
const nameAliases = ['药品名称', '药名', 'drug_name', '名称'];
for (let i = 0; i < n; i++) {
const row = rows[i];
if (!row || typeof row !== 'object') {
invalidCount++;
continue;
}
let drugNumber =
pickByHeaderAliases(row, numberAliases) ||
normalizeCell(row['药品编号']) ||
normalizeCell(row['药品编码']) ||
normalizeCell(row['编号']) ||
normalizeCell(row['drug_number']);
let drugName =
pickByHeaderAliases(row, nameAliases) ||
normalizeCell(row['药品名称']) ||
normalizeCell(row['药名']) ||
normalizeCell(row['drug_name']);
if (!drugName || !drugNumber) {
const empty = !Object.values(row).some(
(v) => normalizeCell(v) !== '',
);
if (empty) {
continue;
}
invalidCount++;
continue;
}
items.push({
drug_name: drugName,
drug_number: drugNumber,
});
}
return { items, invalidCount };
}
/**
* 标准「药品编码」表:第 1 列为编号、第 2 列为名称(表头行自动跳过)
*/
function parseRowsByPosition(sheet: XLSX.WorkSheet): {
items: ParsedDrugNumberRow[];
invalidCount: number;
rowCount: number;
} {
const aoa = XLSX.utils.sheet_to_json(sheet, {
header: 1,
defval: '',
}) as unknown[][];
const items: ParsedDrugNumberRow[] = [];
let invalidCount = 0;
if (aoa.length === 0) {
return { items: [], invalidCount: 0, rowCount: 0 };
}
let start = 0;
const h0 = normalizeCell(aoa[0]?.[0]);
const h1 = normalizeCell(aoa[0]?.[1]);
if (
h0 &&
(h0.includes('编号') ||
h0.includes('编码') ||
h0.includes('代码') ||
/药品/.test(h0))
) {
start = 1;
}
if (
h1 &&
(h1.includes('名称') || h1.includes('药名') || h1.includes('品名'))
) {
start = 1;
}
const limit = Math.min(aoa.length, start + MAX_ROWS);
for (let i = start; i < limit; i++) {
const row = aoa[i];
if (!row?.length) {
continue;
}
const drugNumber = normalizeCell(row[0]);
const drugName = normalizeCell(row[1]);
if (!drugName || !drugNumber) {
const anyCell = row.some((c) => normalizeCell(c) !== '');
if (anyCell) {
invalidCount++;
}
continue;
}
items.push({ drug_name: drugName, drug_number: drugNumber });
}
return { items, invalidCount, rowCount: aoa.length };
}
/**
* 从第一张工作表解析「药品编号」「药品名称」列(兼容表头别名 + 列位置回退)
*/
export function parseDrugNumberExcelBuffer(buffer: ArrayBuffer): {
items: ParsedDrugNumberRow[];
invalidCount: number;
rowCount: number;
} {
const workbook = XLSX.read(buffer, { type: 'array' });
const sheetName = workbook.SheetNames[0];
if (!sheetName) {
return { items: [], invalidCount: 0, rowCount: 0 };
}
const sheet = workbook.Sheets[sheetName];
if (!sheet) {
return { items: [], invalidCount: 0, rowCount: 0 };
}
const rows = XLSX.utils.sheet_to_json<Record<string, unknown>>(sheet, {
defval: '',
});
let { items, invalidCount } = parseRowsByObjectKeys(rows);
let rowCount = rows.length;
if (items.length === 0) {
const pos = parseRowsByPosition(sheet);
items = pos.items;
invalidCount = pos.invalidCount;
rowCount = pos.rowCount;
}
return {
items,
invalidCount,
rowCount,
};
}
/**
* 与后端 `normalizeDrugNumberForBatch` 对齐,用于对比是否变更
*/
export function normalizeDrugNumberForCompare(value: unknown): string {
if (value === null || value === undefined || value === '') {
return '';
}
if (typeof value === 'boolean') {
return '';
}
if (typeof value === 'number') {
if (Number.isInteger(value) || Math.floor(value) === value) {
return String(Math.trunc(value));
}
return String(value).trim();
}
return String(value).trim();
}
export function isDrugNumberChanged(
oldVal: unknown,
newVal: unknown,
): boolean {
return normalizeDrugNumberForCompare(oldVal) !== normalizeDrugNumberForCompare(newVal);
}

View File

@@ -221,10 +221,10 @@ function getDrugUseListByWesternModal() {
getDrugUseListByWesternModal();
const selectProductId = ref(0);
const selectProductIndex = ref(-1);
function selectProductChange(id) {
selectProductId.value = id;
selectProductIndex.value = id;
}
/**
@@ -232,16 +232,18 @@ function selectProductChange(id) {
* @param id
*/
function selectDrugUseWayChange(id) {
currentDrugs.value = currentDrugs.value.map((item) =>
// 修改item下面drug的 frequency_id = id
item.index_id === selectProductId.value
? {
...item,
way_id: id,
use_ways: drugUseWay.value.find((value) => value.id === id),
}
: item,
);
if (
selectProductIndex.value < 0 ||
selectProductIndex.value >= currentDrugs.value.length
) {
return;
}
const targetDrug = currentDrugs.value[selectProductIndex.value];
if (!targetDrug) {
return;
}
targetDrug.way_id = id;
targetDrug.use_ways = drugUseWay.value.find((value) => value.id === id);
updateLocalStorage();
}
@@ -1945,7 +1947,7 @@ watch(
<Row>
<Col
v-for="(drug, index) in currentDrugs"
:key="drug.id"
:key="`${drug.index_id || drug.id}-${index}`"
:lg="12"
:md="24"
:sm="24"
@@ -2000,7 +2002,7 @@ watch(
placeholder="用法"
@change="selectDrugUseWayChange"
@dropdown-visible-change="
selectProductChange(drug.index_id)
selectProductChange(index)
"
>
<SelectOption :value="0">煎服</SelectOption>