feat: 金方相关
feat: 患者信息身份证改成非必填
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 药品详情弹窗:展示基础信息;管理员/超管可逐条添加/删除别名
|
||||
* 药品详情弹窗
|
||||
* 一级 Tab:基础信息(原详情+别名)/ 仓库信息(总仓+门店改价)
|
||||
*/
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
@@ -15,10 +16,12 @@ import {
|
||||
Input,
|
||||
Popconfirm,
|
||||
Spin,
|
||||
Tabs,
|
||||
Tag,
|
||||
message,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import DrugWarehousePanel from './drug-warehouse-panel.vue';
|
||||
import {
|
||||
addDrugAlias,
|
||||
deleteDrugAlias,
|
||||
@@ -43,6 +46,11 @@ const canManage = ref(false);
|
||||
/** 新增输入框 */
|
||||
const newAlias = ref('');
|
||||
const gridApiRef = ref<any>(null);
|
||||
/** 一级 Tab:基础信息 / 仓库信息 */
|
||||
const mainTab = ref('basic');
|
||||
const warehousePanelRef = ref<InstanceType<typeof DrugWarehousePanel> | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const titleText = computed(
|
||||
() => `药品详情${drug.value?.drug_name ? ` · ${drug.value.drug_name}` : ''}`,
|
||||
@@ -50,6 +58,8 @@ const titleText = computed(
|
||||
|
||||
const aliasCountText = computed(() => `共 ${aliases.value.length} 条`);
|
||||
|
||||
const drugId = computed(() => Number(drug.value?.id || 0));
|
||||
|
||||
/**
|
||||
* 用接口结果回填本地状态
|
||||
*/
|
||||
@@ -60,13 +70,13 @@ function applyDetail(res: any) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载详情
|
||||
* 加载基础详情
|
||||
*/
|
||||
async function loadDetail(drugId: number) {
|
||||
async function loadDetail(id: number) {
|
||||
loading.value = true;
|
||||
newAlias.value = '';
|
||||
try {
|
||||
const res = await getDrugAliasDetail(drugId);
|
||||
const res = await getDrugAliasDetail(id);
|
||||
applyDetail(res);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -79,7 +89,7 @@ async function loadDetail(drugId: number) {
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
class: 'w-[720px]',
|
||||
class: 'w-[900px]',
|
||||
draggable: true,
|
||||
fullscreenButton: false,
|
||||
showConfirmButton: false,
|
||||
@@ -87,6 +97,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
newAlias.value = '';
|
||||
mainTab.value = 'basic';
|
||||
return;
|
||||
}
|
||||
const data = modalApi.getData<{
|
||||
@@ -96,12 +107,22 @@ const [Modal, modalApi] = useVbenModal({
|
||||
}>();
|
||||
gridApiRef.value = data?.gridApi || null;
|
||||
const id = Number(data?.drug_id || data?.id || 0);
|
||||
mainTab.value = 'basic';
|
||||
if (id > 0) {
|
||||
void loadDetail(id);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 切换到仓库信息时刷新一次,避免打开后数据过期
|
||||
*/
|
||||
function onMainTabChange(key: string | number) {
|
||||
if (key === 'warehouse' && drugId.value > 0) {
|
||||
warehousePanelRef.value?.reload?.();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一条别名(支持一次输入多个,按分隔符拆)
|
||||
*/
|
||||
@@ -135,12 +156,12 @@ async function handleAddAlias() {
|
||||
* 删除一条别名
|
||||
*/
|
||||
async function handleDeleteAlias(row: AliasRow) {
|
||||
const drugId = Number(drug.value?.id || 0);
|
||||
const id = Number(drug.value?.id || 0);
|
||||
const aliasId = Number(row?.id || 0);
|
||||
if (!drugId || !aliasId || !canManage.value) return;
|
||||
if (!id || !aliasId || !canManage.value) return;
|
||||
deletingId.value = aliasId;
|
||||
try {
|
||||
const res = await deleteDrugAlias(drugId, aliasId);
|
||||
const res = await deleteDrugAlias(id, aliasId);
|
||||
applyDetail(res);
|
||||
message.success('已删除');
|
||||
gridApiRef.value?.query?.();
|
||||
@@ -171,90 +192,101 @@ async function handleDeleteAlias(row: AliasRow) {
|
||||
</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>
|
||||
<Tabs
|
||||
v-model:active-key="mainTab"
|
||||
class="mt-3"
|
||||
@change="onMainTabChange"
|
||||
>
|
||||
<Tabs.TabPane key="basic" tab="基础信息">
|
||||
<Descriptions bordered size="small" :column="2">
|
||||
<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 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 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>
|
||||
<Popconfirm
|
||||
v-if="canManage"
|
||||
title="确认删除该别名?"
|
||||
ok-text="删除"
|
||||
cancel-text="取消"
|
||||
@confirm="handleDeleteAlias(a)"
|
||||
>
|
||||
|
||||
<div v-if="canManage" class="drug-detail__alias-add">
|
||||
<Input
|
||||
v-model:value="newAlias"
|
||||
allow-clear
|
||||
placeholder="输入别名后回车添加;多个可用顿号/逗号分隔"
|
||||
:disabled="adding"
|
||||
@press-enter="handleAddAlias"
|
||||
/>
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
size="small"
|
||||
:loading="deletingId === Number(a.id || 0)"
|
||||
type="primary"
|
||||
class="ml-2"
|
||||
:loading="adding"
|
||||
@click="handleAddAlias"
|
||||
>
|
||||
删除
|
||||
添加
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="warehouse" tab="仓库信息">
|
||||
<DrugWarehousePanel ref="warehousePanelRef" :drug-id="drugId" />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
<Empty v-else-if="!loading" description="未找到药品" />
|
||||
</Spin>
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 药品详情「仓库信息」面板
|
||||
* 子 Tab:总仓(只读展示)/ 门店(可改供货价与售价)
|
||||
*/
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Descriptions,
|
||||
DescriptionsItem,
|
||||
Empty,
|
||||
InputNumber,
|
||||
Spin,
|
||||
Table,
|
||||
Tabs,
|
||||
Tag,
|
||||
message,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
getDrugWarehouseInfo,
|
||||
updateStorePricesByDrug,
|
||||
} from '#/views/business/warehouse-drug-management/admin/api';
|
||||
|
||||
const props = defineProps<{
|
||||
drugId: number;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const warehouseTab = ref('central');
|
||||
const central = ref<Record<string, any> | null>(null);
|
||||
const stores = ref<Record<string, any>[]>([]);
|
||||
const deliveryBinds = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**
|
||||
* 拉取总仓/门店/配送仓绑定
|
||||
*/
|
||||
async function loadWarehouse() {
|
||||
const id = Number(props.drugId || 0);
|
||||
if (id < 1) {
|
||||
central.value = null;
|
||||
stores.value = [];
|
||||
deliveryBinds.value = [];
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getDrugWarehouseInfo(id);
|
||||
central.value = res?.central || null;
|
||||
stores.value = (res?.stores || []).map((s: any) => ({
|
||||
...s,
|
||||
_buy_price: Number(s.buy_price || 0),
|
||||
_price: Number(s.price || 0),
|
||||
_dirty: false,
|
||||
}));
|
||||
deliveryBinds.value = res?.delivery_binds || [];
|
||||
} catch {
|
||||
central.value = null;
|
||||
stores.value = [];
|
||||
deliveryBinds.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function markDirty(row: Record<string, any>) {
|
||||
row._dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存已改动的门店价格
|
||||
*/
|
||||
async function saveStorePrices() {
|
||||
const dirty = stores.value.filter((s) => s._dirty);
|
||||
if (dirty.length === 0) {
|
||||
message.info('没有需要保存的修改');
|
||||
return;
|
||||
}
|
||||
for (const row of dirty) {
|
||||
if (Number(row._buy_price) < 0 || Number(row._price) < 0) {
|
||||
message.warning(`门店「${row.store_name}」价格不能为负`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await updateStorePricesByDrug({
|
||||
drug_id: Number(props.drugId),
|
||||
items: dirty.map((s) => ({
|
||||
id: Number(s.id),
|
||||
buy_price: Number(s._buy_price),
|
||||
price: Number(s._price),
|
||||
})),
|
||||
});
|
||||
message.success(`已更新 ${dirty.length} 家门店价格`);
|
||||
await loadWarehouse();
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const storeColumns = [
|
||||
{ title: '门店', dataIndex: 'store_name', key: 'store_name', ellipsis: true },
|
||||
{ title: '供货价', key: 'buy_price', width: 140 },
|
||||
{ title: '售价', key: 'price', width: 140 },
|
||||
{ title: '状态', key: 'status', width: 80 },
|
||||
];
|
||||
|
||||
watch(
|
||||
() => props.drugId,
|
||||
() => {
|
||||
void loadWarehouse();
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
void loadWarehouse();
|
||||
});
|
||||
|
||||
defineExpose({ reload: loadWarehouse });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Spin :spinning="loading">
|
||||
<Tabs v-model:active-key="warehouseTab" size="small">
|
||||
<Tabs.TabPane key="central" tab="总仓">
|
||||
<Empty
|
||||
v-if="!central"
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
description="未入总仓"
|
||||
/>
|
||||
<Descriptions v-else bordered size="small" :column="2">
|
||||
<DescriptionsItem label="总仓ID">
|
||||
{{ central.id }}
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="状态">
|
||||
<Tag :color="central.status === 2 ? 'green' : 'red'">
|
||||
{{ central.status_txt || '-' }}
|
||||
</Tag>
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="供货价">
|
||||
¥{{ Number(central.market_price || 0).toFixed(4) }}
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="建议售价">
|
||||
¥{{ Number(central.price || 0).toFixed(4) }}
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="库存">
|
||||
{{ central.stock ?? '-' }}
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="更新时间">
|
||||
{{ central.updated_at || '-' }}
|
||||
</DescriptionsItem>
|
||||
</Descriptions>
|
||||
<div v-if="deliveryBinds.length" class="mt-3 text-xs" style="color: hsl(var(--muted-foreground))">
|
||||
已绑定 {{ deliveryBinds.length }} 个配送仓(改总仓售价时需重分配三费)
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="stores" tab="门店">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-xs" style="color: hsl(var(--muted-foreground))">
|
||||
共 {{ stores.length }} 家门店;修改后点保存
|
||||
</span>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="saving"
|
||||
:disabled="!stores.some((s) => s._dirty)"
|
||||
@click="saveStorePrices"
|
||||
>
|
||||
保存改价
|
||||
</Button>
|
||||
</div>
|
||||
<Empty
|
||||
v-if="stores.length === 0"
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
description="暂无门店价格"
|
||||
/>
|
||||
<Table
|
||||
v-else
|
||||
size="small"
|
||||
:pagination="false"
|
||||
:columns="storeColumns"
|
||||
:data-source="stores"
|
||||
:scroll="{ y: 320 }"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'buy_price'">
|
||||
<InputNumber
|
||||
v-model:value="record._buy_price"
|
||||
:min="0"
|
||||
:precision="4"
|
||||
class="w-full"
|
||||
size="small"
|
||||
@change="markDirty(record)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'price'">
|
||||
<InputNumber
|
||||
v-model:value="record._price"
|
||||
:min="0"
|
||||
:precision="4"
|
||||
class="w-full"
|
||||
size="small"
|
||||
@change="markDirty(record)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<Tag :color="record.status === 2 ? 'green' : 'default'">
|
||||
{{ record.status === 2 ? '上架' : '下架' }}
|
||||
</Tag>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Spin>
|
||||
</template>
|
||||
@@ -161,3 +161,34 @@ export async function applyBatchPriceApi(data: {
|
||||
new_count: number;
|
||||
}>(`${prefix}apply-batch-price`, data);
|
||||
}
|
||||
|
||||
/** 药品仓库信息:总仓 + 门店价 + 配送仓绑定 */
|
||||
export async function getDrugWarehouseInfo(drugId: number) {
|
||||
return requestClient.get<any>(`${prefix}drug-warehouse-info`, {
|
||||
params: { drug_id: drugId },
|
||||
});
|
||||
}
|
||||
|
||||
/** 按药品批量改门店供货价/售价 */
|
||||
export async function updateStorePricesByDrug(data: {
|
||||
drug_id: number;
|
||||
items: Array<{ id: number; price: number; buy_price: number }>;
|
||||
}) {
|
||||
return requestClient.post<any>(`${prefix}update-store-prices-by-drug`, data);
|
||||
}
|
||||
|
||||
/** 总仓改价并重分配配送仓三费 */
|
||||
export async function updateWarehouseWithDeliveryFees(data: {
|
||||
id: number;
|
||||
drug_id?: number;
|
||||
market_price: number;
|
||||
price: number;
|
||||
delivery_binds: Array<{
|
||||
id: number;
|
||||
quote: number;
|
||||
promo_fee: number;
|
||||
platform_fee: number;
|
||||
}>;
|
||||
}) {
|
||||
return requestClient.post<any>(`${prefix}update-with-delivery-fees`, data);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 总仓改价后:强制重分配配送仓三费
|
||||
* 不可点击遮罩关闭,必须确认或取消
|
||||
*/
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Alert, InputNumber, Table, message } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
calcFeeSum,
|
||||
calcPromoFee,
|
||||
} from '#/views/system/delivery-warehouse-drug/config/form';
|
||||
|
||||
import { updateWarehouseWithDeliveryFees } from '../api';
|
||||
|
||||
type BindRow = {
|
||||
id: number;
|
||||
warehouse_id: number;
|
||||
warehouse_name: string;
|
||||
quote: number;
|
||||
promo_fee: number;
|
||||
platform_fee: number;
|
||||
};
|
||||
|
||||
const rows = ref<BindRow[]>([]);
|
||||
const salePrice = ref(0);
|
||||
const marketPrice = ref(0);
|
||||
const centralId = ref(0);
|
||||
const drugId = ref(0);
|
||||
const gridApi = ref<any>(null);
|
||||
const onSuccess = ref<(() => void) | null>(null);
|
||||
|
||||
const saleText = computed(() => `¥${Number(salePrice.value || 0).toFixed(4)}`);
|
||||
|
||||
const columns = [
|
||||
{ title: '配送仓', dataIndex: 'warehouse_name', key: 'warehouse_name', ellipsis: true },
|
||||
{ title: '报价', key: 'quote', width: 130 },
|
||||
{ title: '平台费', key: 'platform_fee', width: 130 },
|
||||
{ title: '推广费', key: 'promo_fee', width: 130 },
|
||||
{ title: '合计', key: 'sum', width: 110 },
|
||||
];
|
||||
|
||||
/**
|
||||
* 售价变化后自动回填推广费 = 售价 − 报价 − 平台费
|
||||
*/
|
||||
function syncPromo(row: BindRow) {
|
||||
row.promo_fee = calcPromoFee({
|
||||
sale_price: salePrice.value,
|
||||
quote: row.quote,
|
||||
platform_fee: row.platform_fee,
|
||||
promo_fee: row.promo_fee,
|
||||
});
|
||||
}
|
||||
|
||||
function feeSum(row: BindRow) {
|
||||
return Number(
|
||||
calcFeeSum({
|
||||
quote: row.quote,
|
||||
promo_fee: row.promo_fee,
|
||||
platform_fee: row.platform_fee,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
class: 'w-[820px]',
|
||||
draggable: true,
|
||||
fullscreenButton: false,
|
||||
// 禁止点遮罩关闭,避免漏填三费
|
||||
closeOnClickModal: false,
|
||||
closeOnPressEscape: false,
|
||||
confirmText: '确认并保存',
|
||||
onCancel() {
|
||||
modalApi.close();
|
||||
},
|
||||
async onConfirm() {
|
||||
if (!rows.value.length) {
|
||||
message.warning('没有需要重分配的配送仓');
|
||||
return;
|
||||
}
|
||||
for (const row of rows.value) {
|
||||
const sum = feeSum(row);
|
||||
if (!(sum > 0)) {
|
||||
message.warning(`配送仓「${row.warehouse_name}」三费合计必须大于0`);
|
||||
return;
|
||||
}
|
||||
if (salePrice.value > 0 && sum - salePrice.value > 0.00005) {
|
||||
message.warning(
|
||||
`配送仓「${row.warehouse_name}」三费合计不能超过售价 ${saleText.value}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
modalApi.setState({ confirmLoading: true, loading: true });
|
||||
try {
|
||||
await updateWarehouseWithDeliveryFees({
|
||||
id: centralId.value,
|
||||
drug_id: drugId.value,
|
||||
market_price: marketPrice.value,
|
||||
price: salePrice.value,
|
||||
delivery_binds: rows.value.map((r) => ({
|
||||
id: r.id,
|
||||
quote: Number(r.quote),
|
||||
promo_fee: Number(r.promo_fee),
|
||||
platform_fee: Number(r.platform_fee),
|
||||
})),
|
||||
});
|
||||
message.success('已更新总仓价格并重分配配送仓三费');
|
||||
gridApi.value?.query?.() ?? gridApi.value?.reload?.();
|
||||
onSuccess.value?.();
|
||||
modalApi.close();
|
||||
} finally {
|
||||
modalApi.setState({ confirmLoading: false, loading: false });
|
||||
}
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
rows.value = [];
|
||||
onSuccess.value = null;
|
||||
return;
|
||||
}
|
||||
const data = modalApi.getData<Record<string, any>>() || {};
|
||||
centralId.value = Number(data.central_id || data.id || 0);
|
||||
drugId.value = Number(data.drug_id || 0);
|
||||
salePrice.value = Number(data.price || 0);
|
||||
marketPrice.value = Number(data.market_price || 0);
|
||||
gridApi.value = data.gridApi || null;
|
||||
onSuccess.value = data.onSuccess || null;
|
||||
const binds = Array.isArray(data.delivery_binds) ? data.delivery_binds : [];
|
||||
rows.value = binds.map((b: any) => {
|
||||
const row: BindRow = {
|
||||
id: Number(b.id),
|
||||
warehouse_id: Number(b.warehouse_id),
|
||||
warehouse_name: String(b.warehouse_name || `仓#${b.warehouse_id}`),
|
||||
quote: Number(b.quote || 0),
|
||||
platform_fee: Number(b.platform_fee || 0),
|
||||
promo_fee: Number(b.promo_fee || 0),
|
||||
};
|
||||
// 按新售价重算推广费默认值
|
||||
if (salePrice.value > 0) {
|
||||
if (!(row.platform_fee > 0)) {
|
||||
row.platform_fee = Number((salePrice.value * 0.05).toFixed(4));
|
||||
}
|
||||
syncPromo(row);
|
||||
}
|
||||
return row;
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal title="重分配配送仓三费">
|
||||
<Alert
|
||||
type="warning"
|
||||
show-icon
|
||||
class="mb-3"
|
||||
message="该药品已绑定配送仓,总仓改价后必须重新填写各仓报价/平台费/推广费后才能保存"
|
||||
:description="`新建议售价 ${saleText};推广费默认=售价−报价−平台费,可微调`"
|
||||
/>
|
||||
<Table
|
||||
size="small"
|
||||
:pagination="false"
|
||||
:columns="columns"
|
||||
:data-source="rows"
|
||||
:scroll="{ y: 360 }"
|
||||
row-key="id"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'quote'">
|
||||
<InputNumber
|
||||
v-model:value="record.quote"
|
||||
:min="0"
|
||||
:precision="4"
|
||||
class="w-full"
|
||||
size="small"
|
||||
@change="syncPromo(record)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'platform_fee'">
|
||||
<InputNumber
|
||||
v-model:value="record.platform_fee"
|
||||
:min="0"
|
||||
:precision="4"
|
||||
class="w-full"
|
||||
size="small"
|
||||
@change="syncPromo(record)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'promo_fee'">
|
||||
<InputNumber
|
||||
v-model:value="record.promo_fee"
|
||||
:min="0"
|
||||
:precision="4"
|
||||
class="w-full"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'sum'">
|
||||
<span
|
||||
:style="
|
||||
salePrice > 0 && feeSum(record) - salePrice > 0.00005
|
||||
? { color: 'hsl(var(--destructive))' }
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
¥{{ feeSum(record).toFixed(4) }}
|
||||
</span>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -1,4 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 总仓药品新增/编辑
|
||||
* 编辑且该药已绑配送仓时:打开不可点遮罩关闭的三费重分配确认窗
|
||||
*/
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
@@ -6,51 +10,63 @@ import { useVbenModal } from '@vben/common-ui';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { getDeliveryWarehouseDrugList } from '#/views/system/delivery-warehouse-drug/api';
|
||||
|
||||
import {
|
||||
createWarehouseDrugManagement,
|
||||
getWarehouseDrugManagementInfo,
|
||||
updateWarehouseDrugManagement,
|
||||
} from '../api';
|
||||
import { modalFormProps } from '../config/form';
|
||||
import { getDrugUseList } from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
const drugTime = ref([]);
|
||||
const drugType = ref([]);
|
||||
const drugUnit = ref([]);
|
||||
const drugFrequency = ref([]);
|
||||
// getDrugUseList().then((res) => {
|
||||
// drugTime.value = res.drug_time.map((item) => {
|
||||
// return {
|
||||
// label: item.name,
|
||||
// value: item.id,
|
||||
// };
|
||||
// });
|
||||
// drugType.value = res.drug_use_type.map((item) => {
|
||||
// return {
|
||||
// label: item.name,
|
||||
// value: item.id,
|
||||
// };
|
||||
// });
|
||||
// drugUnit.value = res.drug_unit.map((item) => {
|
||||
// return {
|
||||
// label: item.name,
|
||||
// value: item.id,
|
||||
// };
|
||||
// });
|
||||
// drugFrequency.value = res.drug_use_frequency.map((item) => {
|
||||
// return {
|
||||
// label: item.name,
|
||||
// value: item.id,
|
||||
// };
|
||||
// });
|
||||
// return res;
|
||||
// });
|
||||
import DeliveryFeeReallocateModalDemo from './delivery-fee-reallocate-modal.vue';
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const gridApi = ref();
|
||||
|
||||
const [Form, formApi] = useVbenForm(modalFormProps);
|
||||
|
||||
const [FeeModal, feeModalApi] = useVbenModal({
|
||||
connectedComponent: DeliveryFeeReallocateModalDemo,
|
||||
});
|
||||
|
||||
/**
|
||||
* 查询该药品是否已绑定配送仓
|
||||
*/
|
||||
async function fetchDeliveryBinds(drugId: number) {
|
||||
if (drugId < 1) return [];
|
||||
const res = await getDeliveryWarehouseDrugList({
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
drug_id: drugId,
|
||||
});
|
||||
return res?.items || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通保存(无配送仓绑定 / 新增)
|
||||
*/
|
||||
async function submitPlain(values: Record<string, any>) {
|
||||
modalApi.setState({ loading: true, confirmLoading: true });
|
||||
try {
|
||||
const submitApi = isUpdate.value
|
||||
? updateWarehouseDrugManagement
|
||||
: createWarehouseDrugManagement;
|
||||
await submitApi(values);
|
||||
message.success('保存成功');
|
||||
gridApi.value?.reload?.() ?? gridApi.value?.query?.();
|
||||
modalApi.close();
|
||||
} finally {
|
||||
modalApi.setState({ loading: false, confirmLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开三费重分配确认(禁止点遮罩关闭)
|
||||
*/
|
||||
function openFeeReallocate(payload: Record<string, any>) {
|
||||
feeModalApi.setData(payload);
|
||||
feeModalApi.open();
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
@@ -60,80 +76,87 @@ const [Modal, modalApi] = useVbenModal({
|
||||
},
|
||||
onConfirm: async () => {
|
||||
formApi.validate().then(async (e: any) => {
|
||||
if (e.valid) {
|
||||
const values = await formApi.getValues();
|
||||
modalApi.setState({ loading: true, confirmLoading: true });
|
||||
const submitApi = isUpdate.value
|
||||
? updateWarehouseDrugManagement
|
||||
: createWarehouseDrugManagement;
|
||||
submitApi(values)
|
||||
.then(() => {
|
||||
message.success('保存成功');
|
||||
gridApi.value?.reload();
|
||||
modalApi.close();
|
||||
})
|
||||
.finally(() => {
|
||||
modalApi.setState({ loading: false, confirmLoading: false });
|
||||
});
|
||||
if (!e.valid) return;
|
||||
const values = await formApi.getValues();
|
||||
if (!isUpdate.value) {
|
||||
await submitPlain(values);
|
||||
return;
|
||||
}
|
||||
// 商品列表点总仓售价时可能只带了总仓 id,补齐 drug_id
|
||||
let drugId = Number(values.drug_id || 0);
|
||||
modalApi.setState({ loading: true, confirmLoading: true });
|
||||
try {
|
||||
if (!drugId && values.id) {
|
||||
const info = await getWarehouseDrugManagementInfo(Number(values.id));
|
||||
drugId = Number(info?.drug_id || 0);
|
||||
values.drug_id = drugId;
|
||||
}
|
||||
const binds = await fetchDeliveryBinds(drugId);
|
||||
if (!binds.length) {
|
||||
await submitPlain(values);
|
||||
return;
|
||||
}
|
||||
modalApi.close();
|
||||
openFeeReallocate({
|
||||
id: Number(values.id),
|
||||
central_id: Number(values.id),
|
||||
drug_id: drugId,
|
||||
market_price: Number(values.market_price),
|
||||
price: Number(values.price),
|
||||
delivery_binds: binds.map((b: any) => ({
|
||||
id: b.id,
|
||||
warehouse_id: b.warehouse_id,
|
||||
warehouse_name:
|
||||
b.warehouse?.name || b.warehouse_name || `仓#${b.warehouse_id}`,
|
||||
quote: b.quote,
|
||||
promo_fee: b.promo_fee,
|
||||
platform_fee: b.platform_fee,
|
||||
})),
|
||||
gridApi: gridApi.value,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
modalApi.setState({ loading: false, confirmLoading: false });
|
||||
}
|
||||
});
|
||||
await formApi.validateAndSubmitForm();
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
|
||||
if (isOpen) {
|
||||
const modalData = modalApi.getData<{
|
||||
gridApi?: unknown;
|
||||
listDrugType?: number;
|
||||
update?: boolean;
|
||||
values?: Record<string, any>;
|
||||
}>();
|
||||
const listDrugType = modalData?.listDrugType ?? 1;
|
||||
|
||||
formApi.updateSchema([
|
||||
{
|
||||
fieldName: 'drug_id',
|
||||
component: 'WarehouseAdminDrugSearch' as const,
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
productType: listDrugType,
|
||||
},
|
||||
if (!isOpen) return;
|
||||
const modalData = modalApi.getData<{
|
||||
gridApi?: unknown;
|
||||
listDrugType?: number;
|
||||
update?: boolean;
|
||||
values?: Record<string, any>;
|
||||
}>();
|
||||
const listDrugType = modalData?.listDrugType ?? 1;
|
||||
formApi.updateSchema([
|
||||
{
|
||||
fieldName: 'drug_id',
|
||||
component: 'WarehouseAdminDrugSearch' as const,
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
productType: listDrugType,
|
||||
},
|
||||
{
|
||||
componentProps: {
|
||||
options: drugTime.value,
|
||||
},
|
||||
fieldName: 'time_id',
|
||||
},
|
||||
{
|
||||
componentProps: {
|
||||
options: drugUnit.value,
|
||||
},
|
||||
fieldName: 'unit_id',
|
||||
},
|
||||
{
|
||||
componentProps: {
|
||||
options: drugType.value,
|
||||
},
|
||||
fieldName: 'type_id',
|
||||
},
|
||||
{
|
||||
componentProps: {
|
||||
options: drugFrequency.value,
|
||||
},
|
||||
fieldName: 'frequency_id',
|
||||
},
|
||||
]);
|
||||
const { values, update } = modalData ?? {};
|
||||
if (values) {
|
||||
isUpdate.value = !!update;
|
||||
formApi.setValues(values);
|
||||
}
|
||||
},
|
||||
]);
|
||||
const { values, update } = modalData ?? {};
|
||||
if (values) {
|
||||
isUpdate.value = !!update;
|
||||
formApi.setValues(values);
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
formApi.resetForm();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- FeeModal 与编辑窗平级,避免编辑窗关闭后确认窗挂载异常 -->
|
||||
<FeeModal />
|
||||
<Modal
|
||||
:title="`${isUpdate === true ? '编辑' : '新增'}仓库药品`"
|
||||
class="w-[60%] h-[60%]"
|
||||
|
||||
@@ -19,6 +19,9 @@ import {
|
||||
getWarehouseExportDataApi,
|
||||
updateWarehouseDrugManagementStatusApi
|
||||
} from './api';
|
||||
import BindListModalDemo from '#/views/system/delivery-warehouse-drug/components/bind-list-modal.vue';
|
||||
import { resolveCentralPrice } from '#/views/system/delivery-warehouse-drug/utils/resolve-central-price';
|
||||
|
||||
import FormModalDemo from './components/modal.vue';
|
||||
import ExcelUpload from './components/ExcelUpload.vue';
|
||||
import SpreadsheetTableConfigModal from './components/SpreadsheetTableConfigModal.vue';
|
||||
@@ -83,6 +86,10 @@ const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: FormModalDemo,
|
||||
});
|
||||
|
||||
const [BindListModal, bindListModalApi] = useVbenModal({
|
||||
connectedComponent: BindListModalDemo,
|
||||
});
|
||||
|
||||
const [ExcelUploadModal, ExcelUploadModalApi] = useVbenModal({
|
||||
connectedComponent: ExcelUpload,
|
||||
});
|
||||
@@ -111,6 +118,30 @@ function rowProductImageSrc(row: any) {
|
||||
return drug?.image || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开某药品的配送仓绑定列表/新增
|
||||
*/
|
||||
function openBindList(row: Record<string, any>, autoCreate = false) {
|
||||
const drug = row?.drug || {};
|
||||
const drugId = Number(row?.drug_id || drug?.id || 0);
|
||||
if (drugId < 1) {
|
||||
message.warning('无法识别药品');
|
||||
return;
|
||||
}
|
||||
bindListModalApi.setData({
|
||||
drug_id: drugId,
|
||||
drug_name: drug.drug_name || row.drug_name || '',
|
||||
specification: drug.specification || '',
|
||||
image: drug.image || '',
|
||||
// ?? 与 || 混用必须加括号,否则 vue/compiler-sfc 打包报错
|
||||
central_price: (resolveCentralPrice(row) ?? Number(row.price || 0)) || null,
|
||||
values: row,
|
||||
gridApi,
|
||||
autoCreate,
|
||||
});
|
||||
bindListModalApi.open();
|
||||
}
|
||||
|
||||
const showModal = (data = {}, isUpdate = false) => {
|
||||
formModalApi.setData({
|
||||
values: data,
|
||||
@@ -195,6 +226,7 @@ function onSpreadsheetConfigSaved() {
|
||||
<ExcelUploadModal />
|
||||
<TcmPriceBatchModalComp />
|
||||
<FormModal />
|
||||
<BindListModal />
|
||||
<SpreadsheetTableConfigModal
|
||||
v-if="productType === 1"
|
||||
ref="spreadsheetConfigModalRef"
|
||||
@@ -339,6 +371,13 @@ function onSpreadsheetConfigSaved() {
|
||||
// auth: ['western-medicine', 'sys:role:detail'],
|
||||
onClick: showModal.bind(null, row, true),
|
||||
},
|
||||
{
|
||||
label: '配送仓绑定',
|
||||
type: 'link',
|
||||
icon: 'mdi:truck-delivery-outline',
|
||||
size: 'small',
|
||||
onClick: openBindList.bind(null, row, false),
|
||||
},
|
||||
]"
|
||||
:drop-down-actions="[
|
||||
{
|
||||
|
||||
@@ -803,6 +803,39 @@ function handleImportAiMedicalRecord(payload: {
|
||||
|
||||
const aiMedicalRecordModalRef = ref<InstanceType<typeof AiMedicalRecordModal> | null>(null);
|
||||
|
||||
/**
|
||||
* AI「生成病历」弹窗点改字段:即时同步到本页表单(不落库,与 AI 出方预览改病历同链路)
|
||||
*/
|
||||
function handleSyncAiMedicalRecord(partial: Record<string, any>) {
|
||||
patchFields(partial || {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部/弹窗局部回写病历字段,同步表单与本地草稿,不自动落库
|
||||
*/
|
||||
function patchFields(partial: Record<string, any>) {
|
||||
if (!partial || typeof partial !== 'object') return;
|
||||
Object.keys(partial).forEach((k) => {
|
||||
const raw = partial[k];
|
||||
const val = raw == null ? '' : String(raw);
|
||||
if (k === 'diagnosis') {
|
||||
form.diagnosis = val;
|
||||
emit('update:diagnosis', val);
|
||||
return;
|
||||
}
|
||||
if (k === 'doctor_order' || k === 'medicalAdvice') {
|
||||
form.doctor_order = val;
|
||||
emit('update:medicalAdvice', val);
|
||||
return;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(form, k)) {
|
||||
(form as any)[k] = val;
|
||||
}
|
||||
});
|
||||
persistLocalDraft();
|
||||
scheduleSyncToPrescription();
|
||||
}
|
||||
|
||||
async function handleClear() {
|
||||
if (!props.registerId) return;
|
||||
await clearMedicalRecord({
|
||||
@@ -870,31 +903,8 @@ defineExpose({
|
||||
diagnosis: diagnosisModel.value,
|
||||
doctor_order: medicalAdviceModel.value,
|
||||
}),
|
||||
/**
|
||||
* 外部局部回写病历字段(如 AI 出方预览里点改),同步表单与本地草稿,不自动落库
|
||||
*/
|
||||
patchFields: (partial: Record<string, any>) => {
|
||||
if (!partial || typeof partial !== 'object') return;
|
||||
Object.keys(partial).forEach((k) => {
|
||||
const raw = partial[k];
|
||||
const val = raw == null ? '' : String(raw);
|
||||
if (k === 'diagnosis') {
|
||||
form.diagnosis = val;
|
||||
emit('update:diagnosis', val);
|
||||
return;
|
||||
}
|
||||
if (k === 'doctor_order' || k === 'medicalAdvice') {
|
||||
form.doctor_order = val;
|
||||
emit('update:medicalAdvice', val);
|
||||
return;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(form, k)) {
|
||||
(form as any)[k] = val;
|
||||
}
|
||||
});
|
||||
persistLocalDraft();
|
||||
scheduleSyncToPrescription();
|
||||
},
|
||||
/** 外部局部回写(如 AI 出方预览点改) */
|
||||
patchFields,
|
||||
});
|
||||
|
||||
/** source:走中医三典气泡;默认 entry 走病历词条 */
|
||||
@@ -1455,6 +1465,7 @@ const visibleHealthFields = computed(() =>
|
||||
<AiMedicalRecordModal
|
||||
ref="aiMedicalRecordModalRef"
|
||||
@import="handleImportAiMedicalRecord"
|
||||
@sync-medical-record="handleSyncAiMedicalRecord"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -16,9 +16,14 @@ import {
|
||||
aiListGenerations,
|
||||
} from '../api';
|
||||
import { MEDICAL_RECORD_FIELD_OPTIONS } from '../config/constants';
|
||||
import TextareaExpandModal from './TextareaExpandModal.vue';
|
||||
import AiDisclaimerBanner from '#/views/doctor/components/AiDisclaimerBanner.vue';
|
||||
import { ensureAiFeatureConsent } from '#/views/doctor/utils/aiFeatureGate';
|
||||
import PrescriptionLoading from '#/components/loading/PrescriptionLoading.vue';
|
||||
import { getSystemConfigByKeys } from '#/views/system/system-config/api';
|
||||
|
||||
/** 常用|我的展示位置(与病历面板一致,供放大弹窗 chips/气泡) */
|
||||
type MrCommonDisplayMode = 'tags' | 'bubble' | 'both';
|
||||
|
||||
/** 本地「正在生成中」占位 id,不与真实 generation_id 冲突 */
|
||||
const PENDING_GEN_ID = -1;
|
||||
@@ -93,6 +98,8 @@ const emit = defineEmits<{
|
||||
} | null;
|
||||
},
|
||||
): void;
|
||||
/** 生成弹窗里改字段时即时回写病历面板(与 AI 出方 sync-medical-record 同协议) */
|
||||
(e: 'sync-medical-record', payload: Record<string, any>): void;
|
||||
}>();
|
||||
|
||||
const registerId = ref(0);
|
||||
@@ -136,6 +143,33 @@ const previewRows = computed(() =>
|
||||
})).filter((r) => r.value !== ''),
|
||||
);
|
||||
|
||||
/**
|
||||
* 二级「生成病历」弹窗行:主诉始终展示 + 选填字段(空也展示,可点改)
|
||||
* UI 与 AI 出方 GenModal 一致:Descriptions 浏览态 / 点击进入 TextArea
|
||||
*/
|
||||
const genPreviewRows = computed(() => {
|
||||
const rows: Array<{ key: string; label: string }> = [
|
||||
{ key: 'chief_complaint', label: '主诉' },
|
||||
];
|
||||
for (const f of GEN_OPTIONAL_FIELDS) {
|
||||
rows.push({ key: f.value, label: f.label });
|
||||
}
|
||||
return rows;
|
||||
});
|
||||
|
||||
/** 二级弹窗:当前正在编辑的字段 key,空表示浏览态 */
|
||||
const editingKey = ref('');
|
||||
/** 单击延迟定时器:与双击放大区分开,避免双击先误进行内编辑 */
|
||||
let genFieldClickTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
const commonDisplayMode = ref<MrCommonDisplayMode>('tags');
|
||||
const showCommonTags = computed(
|
||||
() => commonDisplayMode.value === 'tags' || commonDisplayMode.value === 'both',
|
||||
);
|
||||
const showCommonInBubble = computed(
|
||||
() =>
|
||||
commonDisplayMode.value === 'bubble' || commonDisplayMode.value === 'both',
|
||||
);
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
title: 'AI写病历',
|
||||
class: 'w-[860px]',
|
||||
@@ -188,6 +222,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
||||
drawerApi.close();
|
||||
return;
|
||||
}
|
||||
void loadCommonDisplayMode();
|
||||
void bootstrapHistory();
|
||||
},
|
||||
});
|
||||
@@ -201,6 +236,11 @@ const [GenModal, genModalApi] = useVbenModal({
|
||||
onConfirm: async () => confirmGenerateAndClose(),
|
||||
});
|
||||
|
||||
/** 与病历面板同一套双击放大编辑 */
|
||||
const [ExpandModal, expandModalApi] = useVbenModal({
|
||||
connectedComponent: TextareaExpandModal,
|
||||
});
|
||||
|
||||
function resetState() {
|
||||
histLoading.value = false;
|
||||
generating.value = false;
|
||||
@@ -213,9 +253,30 @@ function resetState() {
|
||||
chiefComplaint.value = '';
|
||||
genContext.value = emptyGenContext();
|
||||
disclaimerText.value = '';
|
||||
editingKey.value = '';
|
||||
if (genFieldClickTimer) {
|
||||
clearTimeout(genFieldClickTimer);
|
||||
genFieldClickTimer = null;
|
||||
}
|
||||
expandModalApi.close();
|
||||
genModalApi.close();
|
||||
}
|
||||
|
||||
/** 拉取常用词条展示模式,放大弹窗 chips/气泡与病历页一致 */
|
||||
async function loadCommonDisplayMode() {
|
||||
try {
|
||||
const res = await getSystemConfigByKeys(['mr_common_display_mode']);
|
||||
const mode = String(
|
||||
res?.mr_common_display_mode ?? res?.data?.mr_common_display_mode ?? 'tags',
|
||||
) as MrCommonDisplayMode;
|
||||
if (mode === 'tags' || mode === 'bubble' || mode === 'both') {
|
||||
commonDisplayMode.value = mode;
|
||||
}
|
||||
} catch {
|
||||
commonDisplayMode.value = 'tags';
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(ts: number) {
|
||||
const n = Number(ts || 0);
|
||||
if (!n) return '—';
|
||||
@@ -262,9 +323,93 @@ async function bootstrapHistory() {
|
||||
}
|
||||
|
||||
function openGenerateModal() {
|
||||
editingKey.value = '';
|
||||
genModalApi.open();
|
||||
}
|
||||
|
||||
/** 点字段进入行内编辑(与 AI 出方二级弹窗同一交互) */
|
||||
function startEdit(key: string) {
|
||||
editingKey.value = key;
|
||||
}
|
||||
|
||||
function fieldValue(key: string) {
|
||||
if (key === 'chief_complaint') return chiefComplaint.value;
|
||||
return String(genContext.value?.[key] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成弹窗字段编辑:主诉写 chiefComplaint,其余写 genContext
|
||||
* 同时 emit 回写病历面板,避免只改弹窗本地态、父页表单仍是旧值
|
||||
*/
|
||||
function onFieldInput(key: string, val: string) {
|
||||
if (key === 'chief_complaint') {
|
||||
chiefComplaint.value = val;
|
||||
} else {
|
||||
genContext.value = {
|
||||
...genContext.value,
|
||||
[key]: val,
|
||||
};
|
||||
}
|
||||
emit('sync-medical-record', { [key]: val });
|
||||
}
|
||||
|
||||
function endEdit() {
|
||||
editingKey.value = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 单击延迟进项内编辑:若随后是双击则取消,改为打开放大弹窗
|
||||
* (否则双击的第一次 click 会先切到 TextArea,导致 dblclick 丢事件)
|
||||
*/
|
||||
function onGenFieldClick(key: string) {
|
||||
if (genFieldClickTimer) clearTimeout(genFieldClickTimer);
|
||||
genFieldClickTimer = setTimeout(() => {
|
||||
genFieldClickTimer = null;
|
||||
startEdit(key);
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function onGenFieldDblClick(key: string, label: string) {
|
||||
if (genFieldClickTimer) {
|
||||
clearTimeout(genFieldClickTimer);
|
||||
genFieldClickTimer = null;
|
||||
}
|
||||
openGenFieldExpand(key, label);
|
||||
}
|
||||
|
||||
/**
|
||||
* 行内 TextArea 用 mousedown.detail===2 捕获双击
|
||||
* 为什么不用 dblclick:blur 会先卸载 TextArea,导致 dblclick 丢事件
|
||||
*/
|
||||
function onGenTextareaMouseDown(e: MouseEvent, key: string, label: string) {
|
||||
if (e.detail === 2) {
|
||||
e.preventDefault();
|
||||
openGenFieldExpand(key, label);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 双击字段:打开与病历页同一套 TextareaExpandModal(词条搜索 + 常用)
|
||||
* joinMode/source 规则对齐 MedicalRecordPanel.openFieldExpand
|
||||
*/
|
||||
function openGenFieldExpand(key: string, label: string) {
|
||||
editingKey.value = '';
|
||||
const joinMode =
|
||||
key === 'tongue' || key === 'pulse' ? 'replace' : 'newline';
|
||||
expandModalApi.setData({
|
||||
title: label,
|
||||
value: fieldValue(key) || '',
|
||||
onConfirm: (v: string) => onFieldInput(key, v),
|
||||
storeId: storeId.value,
|
||||
source: 'entry',
|
||||
fieldCode: key,
|
||||
showChips: showCommonTags.value,
|
||||
showCommonInBubble: showCommonInBubble.value,
|
||||
joinMode,
|
||||
});
|
||||
expandModalApi.open();
|
||||
}
|
||||
|
||||
async function onSelectHistory(row: any) {
|
||||
if (!row?.id || Number(row.id) === PENDING_GEN_ID || row._pending) return;
|
||||
activeId.value = Number(row.id);
|
||||
@@ -544,13 +689,13 @@ defineExpose({
|
||||
</div>
|
||||
</Drawer>
|
||||
|
||||
<!-- 二级:与 AI 出方一致 — Descriptions 双列紧凑表单 -->
|
||||
<!-- 二级:与 AI 出方一致 — Descriptions 浏览态 + 点击编辑;双击同病历页放大 -->
|
||||
<GenModal>
|
||||
<div class="space-y-2 text-sm text-foreground">
|
||||
<AiDisclaimerBanner :text="disclaimerText" />
|
||||
<div class="text-xs text-muted-foreground">
|
||||
{{ patientName || '—' }} · {{ sexLabel }} · {{ patientAge || '—' }}岁 ·
|
||||
主诉必填;下方选填有内容会作为 AI 参考(若该字段在「病历配置」中需生成则保留原文)
|
||||
主诉必填;单击编辑,双击放大(同病历页);选填有内容会作为 AI 参考
|
||||
</div>
|
||||
<div class="max-h-[58vh] overflow-y-auto">
|
||||
<Descriptions
|
||||
@@ -559,38 +704,44 @@ defineExpose({
|
||||
size="small"
|
||||
class="ai-mr-gen-desc"
|
||||
>
|
||||
<Descriptions.Item label="主诉" :span="2">
|
||||
<Input.TextArea
|
||||
v-model:value="chiefComplaint"
|
||||
:rows="3"
|
||||
placeholder="请确认或补充主诉后再生成"
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
v-for="f in GEN_OPTIONAL_FIELDS"
|
||||
:key="f.value"
|
||||
:label="f.label"
|
||||
:span="1"
|
||||
v-for="row in genPreviewRows"
|
||||
:key="row.key"
|
||||
:label="row.label"
|
||||
:span="FULL_SPAN_KEYS.has(row.key) ? 2 : 1"
|
||||
>
|
||||
<Input.TextArea
|
||||
v-model:value="genContext[f.value]"
|
||||
:rows="2"
|
||||
:placeholder="`选填${f.label}`"
|
||||
v-if="editingKey === row.key"
|
||||
:value="fieldValue(row.key)"
|
||||
:rows="row.key === 'chief_complaint' ? 3 : 2"
|
||||
autofocus
|
||||
:placeholder="`${row.label}(双击放大)`"
|
||||
@update:value="(v) => onFieldInput(row.key, String(v ?? ''))"
|
||||
@blur="endEdit"
|
||||
@mousedown="(e) => onGenTextareaMouseDown(e, row.key, row.label)"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="max-h-24 cursor-text overflow-y-auto whitespace-pre-wrap leading-relaxed"
|
||||
title="单击编辑,双击放大"
|
||||
@click="onGenFieldClick(row.key)"
|
||||
@dblclick.stop.prevent="onGenFieldDblClick(row.key, row.label)"
|
||||
>
|
||||
{{ fieldValue(row.key) || '(空,单击填写 / 双击放大)' }}
|
||||
</div>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</div>
|
||||
<div class="text-xs text-muted-foreground">
|
||||
点击「开始生成」后将关闭本弹窗,请在抽屉中等待结果。
|
||||
</div>
|
||||
</div>
|
||||
</GenModal>
|
||||
<ExpandModal />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* label 宽与 AI 出方 .ai-rx-gen-desc 对齐 */
|
||||
.ai-mr-preview-desc :deep(.ant-descriptions-item-label),
|
||||
.ai-mr-gen-desc :deep(.ant-descriptions-item-label) {
|
||||
width: 88px;
|
||||
width: 110px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.ai-mr-preview-desc :deep(.ant-descriptions-item-content),
|
||||
|
||||
@@ -18,13 +18,21 @@ export async function getDeliveryWarehouseProductInfo(id: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台审核:status 必为 2 下架或 3 上架;需填 zone_id
|
||||
* 平台审核(一次提交):
|
||||
* 主数据审过 + 总仓供货价/售价同步门店 + 绑仓三费 + 可选初始入库
|
||||
*/
|
||||
export async function auditDeliveryWarehouseProduct(data: {
|
||||
id: number;
|
||||
status: 2 | 3;
|
||||
zone_id: number;
|
||||
category_id?: number;
|
||||
drug_number: string;
|
||||
market_price: number;
|
||||
price: number;
|
||||
quote: number;
|
||||
promo_fee: number;
|
||||
platform_fee: number;
|
||||
initial_stock?: number;
|
||||
}) {
|
||||
return requestClient.post<any>(`${prefix}audit`, data);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 平台仓品审核弹窗
|
||||
* 必选上架/下架 + 所属分区;审核成功后由列表页打开仓药绑定弹窗
|
||||
* 平台仓品审核弹窗(单次提交)
|
||||
* 分区/分类/上下架 + ERP + 总仓供货价/售价 + 绑仓三费 + 可选初始库存
|
||||
* 提交后由后端一次性完成主数据审过、总仓同步门店、绑仓与入库
|
||||
*/
|
||||
import { nextTick, ref } from 'vue';
|
||||
import { h, onUnmounted, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Descriptions, Image, message, Tag } from 'ant-design-vue';
|
||||
import { Button, Descriptions, Image, message, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { getDrugCategoriesTree, getZoneOptions } from '#/views/business/product/drug-categories/api';
|
||||
import {
|
||||
calcFeeSum,
|
||||
isFeeOverSalePrice,
|
||||
registerFeeOverTipHandler,
|
||||
resetAutoPromoFeeKey,
|
||||
syncFeeSumDisplay,
|
||||
} from '#/views/system/delivery-warehouse-drug/config/form';
|
||||
|
||||
import {
|
||||
auditDeliveryWarehouseProduct,
|
||||
@@ -19,8 +27,10 @@ import {
|
||||
|
||||
const gridApi = ref();
|
||||
const detail = ref<Record<string, any> | null>(null);
|
||||
/** 审核成功后回传给列表页,用于打开绑仓弹窗 */
|
||||
const onBindPreset = ref<((preset: Record<string, any>) => void) | null>(null);
|
||||
/** 三费超售价红色提示 */
|
||||
const feeOverTip = ref('');
|
||||
/** 记录已按售价自动填过平台费的售价,避免反复覆盖手改 */
|
||||
const lastAutoPlatformSale = ref(0);
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
wrapperClass: 'grid-cols-12',
|
||||
@@ -96,12 +106,153 @@ const [Form, formApi] = useVbenForm({
|
||||
fieldName: 'status',
|
||||
label: '审核结果',
|
||||
rules: 'selectRequired',
|
||||
help: '审核通过后按所选结果上架或下架,并打开绑仓弹窗',
|
||||
help: '主数据上下架;总仓/绑仓状态同步跟随',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '请输入 ERP 编号,无则点右侧「非erp」',
|
||||
allowClear: true,
|
||||
},
|
||||
fieldName: 'drug_number',
|
||||
label: 'ERP编号',
|
||||
rules: 'required',
|
||||
help: '可填真实编号;无 ERP 点「非erp」填入 -1',
|
||||
// 渲染时再点选,此时 formApi 已就绪
|
||||
suffix: () =>
|
||||
h(
|
||||
Button,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'link',
|
||||
onClick: (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
formApi.setFieldValue('drug_number', '-1');
|
||||
},
|
||||
},
|
||||
{ default: () => '非erp' },
|
||||
),
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入仓库供货价',
|
||||
min: 0,
|
||||
precision: 4,
|
||||
class: 'w-full',
|
||||
},
|
||||
fieldName: 'market_price',
|
||||
label: '仓库供货价',
|
||||
rules: 'required',
|
||||
help: '写入总仓并同步到门店供货价',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入售价',
|
||||
min: 0,
|
||||
precision: 4,
|
||||
class: 'w-full',
|
||||
},
|
||||
fieldName: 'price',
|
||||
label: '售价',
|
||||
rules: 'required',
|
||||
help: '写入总仓销售价;三费合计不得超过该值',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入绑定仓库报价',
|
||||
min: 0,
|
||||
precision: 4,
|
||||
class: 'w-full',
|
||||
},
|
||||
fieldName: 'quote',
|
||||
label: '绑仓报价',
|
||||
rules: 'required',
|
||||
help: '默认取仓侧上传报价,可修改',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入平台费',
|
||||
min: 0,
|
||||
precision: 4,
|
||||
class: 'w-full',
|
||||
},
|
||||
fieldName: 'platform_fee',
|
||||
label: '平台费',
|
||||
rules: 'required',
|
||||
help: '默认售价的 5%,可改',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '自动=售价−报价−平台费',
|
||||
min: 0,
|
||||
precision: 4,
|
||||
class: 'w-full',
|
||||
},
|
||||
fieldName: 'promo_fee',
|
||||
label: '推广费',
|
||||
rules: 'required',
|
||||
help: '有售价时自动计算,可微调',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
placeholder: '报价+推广费+平台费',
|
||||
},
|
||||
fieldName: '_fee_sum',
|
||||
label: '费用合计',
|
||||
// 把 price 映射为仓药绑定逻辑里的 sale_price,复用三费联动
|
||||
dependencies: {
|
||||
async trigger(values, formApiInst) {
|
||||
const sale = Number(values.price) || 0;
|
||||
// 售价变化时默认平台费 = 售价 * 5%(仅该售价首次自动填)
|
||||
if (sale > 0 && lastAutoPlatformSale.value !== sale) {
|
||||
const platform = Number((sale * 0.05).toFixed(4));
|
||||
await formApiInst.setFieldValue('platform_fee', platform);
|
||||
lastAutoPlatformSale.value = sale;
|
||||
values = { ...values, platform_fee: platform };
|
||||
}
|
||||
syncFeeSumDisplay(
|
||||
{
|
||||
...values,
|
||||
sale_price: sale,
|
||||
},
|
||||
formApiInst,
|
||||
);
|
||||
},
|
||||
triggerFields: ['quote', 'promo_fee', 'platform_fee', 'price'],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '选填,默认 0',
|
||||
min: 0,
|
||||
precision: 0,
|
||||
class: 'w-full',
|
||||
},
|
||||
fieldName: 'initial_stock',
|
||||
label: '初始库存',
|
||||
help: '大于 0 时绑仓后自动入库并记流水',
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
registerFeeOverTipHandler((tip) => {
|
||||
feeOverTip.value = tip;
|
||||
});
|
||||
onUnmounted(() => {
|
||||
registerFeeOverTipHandler(null);
|
||||
});
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
draggable: true,
|
||||
@@ -121,21 +272,45 @@ const [Modal, modalApi] = useVbenModal({
|
||||
message.warning('请选择所属分区');
|
||||
return;
|
||||
}
|
||||
const drugNumber = String(values.drug_number || '').trim();
|
||||
if (!drugNumber) {
|
||||
message.warning('请填写 ERP 编号(无 ERP 可点「非erp」填 -1)');
|
||||
return;
|
||||
}
|
||||
const feeValues = {
|
||||
quote: values.quote,
|
||||
promo_fee: values.promo_fee,
|
||||
platform_fee: values.platform_fee,
|
||||
sale_price: values.price,
|
||||
};
|
||||
if (isFeeOverSalePrice(feeValues)) {
|
||||
message.warning(
|
||||
`三费合计已超过售价 ¥${Number(values.price).toFixed(4)}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (Number(calcFeeSum(feeValues)) <= 0) {
|
||||
message.warning('报价/推广费/平台费合计必须大于0');
|
||||
return;
|
||||
}
|
||||
modalApi.setState({ loading: true, confirmLoading: true });
|
||||
auditDeliveryWarehouseProduct({
|
||||
id: Number(values.id),
|
||||
status: status as 2 | 3,
|
||||
zone_id: Number(values.zone_id),
|
||||
category_id: Number(values.category_id || 0),
|
||||
drug_number: drugNumber,
|
||||
market_price: Number(values.market_price),
|
||||
price: Number(values.price),
|
||||
quote: Number(values.quote),
|
||||
promo_fee: Number(values.promo_fee),
|
||||
platform_fee: Number(values.platform_fee),
|
||||
initial_stock: Number(values.initial_stock || 0),
|
||||
})
|
||||
.then((res: any) => {
|
||||
message.success('审核成功,请完成仓药绑定');
|
||||
.then(() => {
|
||||
message.success('审核成功,已完成总仓同步与仓药绑定');
|
||||
gridApi.value?.query?.() ?? gridApi.value?.reload?.();
|
||||
const preset = res?.bind_preset || res?.data?.bind_preset;
|
||||
modalApi.close();
|
||||
if (preset && onBindPreset.value) {
|
||||
nextTick(() => onBindPreset.value?.(preset));
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalApi.setState({ loading: false, confirmLoading: false });
|
||||
@@ -145,31 +320,53 @@ const [Modal, modalApi] = useVbenModal({
|
||||
await formApi.validateAndSubmitForm();
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
|
||||
onBindPreset.value = isOpen ? modalApi.getData()?.onBindPreset || null : null;
|
||||
if (!isOpen) {
|
||||
detail.value = null;
|
||||
feeOverTip.value = '';
|
||||
return;
|
||||
}
|
||||
await formApi.resetForm();
|
||||
const data = modalApi.getData<Record<string, any>>() || {};
|
||||
gridApi.value = data.gridApi ?? null;
|
||||
resetAutoPromoFeeKey();
|
||||
lastAutoPlatformSale.value = 0;
|
||||
feeOverTip.value = '';
|
||||
await formApi.resetForm();
|
||||
const id = Number(data.id || data.values?.id || 0);
|
||||
await formApi.setValues({ id, status: undefined, zone_id: undefined, category_id: undefined });
|
||||
let row = data.values || { id };
|
||||
if (id > 0) {
|
||||
const row = data.values || { id };
|
||||
try {
|
||||
const info = await getDeliveryWarehouseProductInfo(id);
|
||||
detail.value = { ...row, ...(info || {}) };
|
||||
row = { ...row, ...(info || {}) };
|
||||
} catch {
|
||||
detail.value = row;
|
||||
// 详情失败仍用列表行
|
||||
}
|
||||
}
|
||||
detail.value = row;
|
||||
const uploadQuote = Number(row.upload_quote || 0);
|
||||
await formApi.setValues({
|
||||
id,
|
||||
status: undefined,
|
||||
zone_id: undefined,
|
||||
category_id: undefined,
|
||||
// 仓侧默认常为 -1,保留回填,也可点「非erp」重填
|
||||
drug_number:
|
||||
row.drug_number != null && String(row.drug_number) !== ''
|
||||
? String(row.drug_number)
|
||||
: '',
|
||||
market_price: undefined,
|
||||
price: undefined,
|
||||
quote: uploadQuote > 0 ? uploadQuote : undefined,
|
||||
platform_fee: undefined,
|
||||
promo_fee: undefined,
|
||||
initial_stock: 0,
|
||||
_fee_sum: uploadQuote > 0 ? String(uploadQuote.toFixed(4)) : '',
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal title="仓品审核" class="w-[80%] md:w-[50%] lg:w-[40%]">
|
||||
<Modal title="仓品审核" class="w-[80%] md:w-[560px] lg:w-[520px]">
|
||||
<div v-if="detail" class="mb-4">
|
||||
<Descriptions :column="1" size="small" bordered>
|
||||
<Descriptions.Item label="药品名称">
|
||||
@@ -196,5 +393,12 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</Descriptions>
|
||||
</div>
|
||||
<Form />
|
||||
<div
|
||||
v-if="feeOverTip"
|
||||
class="mt-2 text-sm"
|
||||
style="color: hsl(var(--destructive))"
|
||||
>
|
||||
{{ feeOverTip }}
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 平台「仓品审核」列表页
|
||||
* 审核通过后自动打开仓药绑定弹窗,预填本仓+药品+报价
|
||||
* 审核弹窗一次提交:主数据 + 总仓同步 + 绑仓三费/入库
|
||||
*/
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
@@ -9,7 +9,6 @@ import { Image, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
import BindModalDemo from '#/views/system/delivery-warehouse-drug/components/modal.vue';
|
||||
|
||||
import AuditModalDemo from './components/audit-modal.vue';
|
||||
import { formOptions } from './config/search';
|
||||
@@ -26,37 +25,6 @@ const [AuditModal, auditModalApi] = useVbenModal({
|
||||
connectedComponent: AuditModalDemo,
|
||||
});
|
||||
|
||||
const [BindModal, bindModalApi] = useVbenModal({
|
||||
connectedComponent: BindModalDemo,
|
||||
});
|
||||
|
||||
/**
|
||||
* 审核成功后打开仓药绑定:锁定药品、预填仓库与报价
|
||||
*/
|
||||
function openBindFromPreset(preset: Record<string, any>) {
|
||||
if (!preset?.drug_id || !preset?.warehouse_id) {
|
||||
return;
|
||||
}
|
||||
bindModalApi.setData({
|
||||
update: false,
|
||||
lockDrug: true,
|
||||
presetDrug: preset.presetDrug || {
|
||||
id: preset.drug_id,
|
||||
drug_name: preset.drug_name,
|
||||
specification: preset.specification,
|
||||
image: preset.image,
|
||||
},
|
||||
values: {
|
||||
warehouse_id: preset.warehouse_id,
|
||||
drug_id: preset.drug_id,
|
||||
quote: preset.quote,
|
||||
status: 2,
|
||||
},
|
||||
gridApi,
|
||||
});
|
||||
bindModalApi.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开审核弹窗(仅待审可点)
|
||||
*/
|
||||
@@ -68,7 +36,6 @@ function openAudit(row: Record<string, any>) {
|
||||
id: row.id,
|
||||
values: row,
|
||||
gridApi,
|
||||
onBindPreset: openBindFromPreset,
|
||||
});
|
||||
auditModalApi.open();
|
||||
}
|
||||
@@ -81,7 +48,6 @@ function auditTagColor(status: number) {
|
||||
<template>
|
||||
<Page auto-content-height title="仓品审核">
|
||||
<AuditModal />
|
||||
<BindModal />
|
||||
<Grid>
|
||||
<template #toolbar-buttons>
|
||||
<TableAction :actions="[]" :drop-down-actions="[]" />
|
||||
|
||||
Reference in New Issue
Block a user