1. 对账单、订单的改良
Some checks failed
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
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
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
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
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
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
@@ -17,6 +17,10 @@ import { debounce } from 'lodash-es';
|
||||
|
||||
import { getProductListDoctorReception } from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
/** 中药无图时与仓库列表一致的占位图 */
|
||||
const TCM_PLACEHOLDER_IMAGE =
|
||||
'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250510/cd470ebf97e31c4048ba502ba71b66a3.jpg';
|
||||
|
||||
// ==================== Props 定义 ====================
|
||||
|
||||
interface Props {
|
||||
@@ -88,6 +92,12 @@ const containerRef = ref<HTMLElement | null>(null);
|
||||
*/
|
||||
const highlightIndex = ref(-1);
|
||||
|
||||
function resolveDropdownImage(item: any): string {
|
||||
if (item._image) return item._image;
|
||||
if (props.type === 1) return TCM_PLACEHOLDER_IMAGE;
|
||||
return '';
|
||||
}
|
||||
|
||||
// ==================== 方法定义 ====================
|
||||
|
||||
/**
|
||||
@@ -296,8 +306,8 @@ watch(
|
||||
<!-- 药品图片 -->
|
||||
<div class="drug-item__image">
|
||||
<img
|
||||
v-if="item._image"
|
||||
:src="item._image"
|
||||
v-if="resolveDropdownImage(item)"
|
||||
:src="resolveDropdownImage(item)"
|
||||
alt=""
|
||||
class="drug-item__img"
|
||||
@error="(e) => (e.target as HTMLImageElement).style.display = 'none'"
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 超级仓库 Admin:选品字段,与常用方「药品搜索」一致(DrugSearchSelect)
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
import { DrugSearchSelect } from '#/components/drug-search-select';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** vben-form:药品 ID */
|
||||
value?: number;
|
||||
/** 列表筛选类型:1 中药,2 西药 */
|
||||
productType?: number;
|
||||
}>(),
|
||||
{
|
||||
productType: 1,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:value': [value: number | undefined];
|
||||
}>();
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const selectedLabel = ref('');
|
||||
|
||||
const searchType = computed(() => (props.productType === 2 ? 2 : 1));
|
||||
|
||||
const storeId = computed(() => userStore.userInfo?.store_id ?? 2);
|
||||
|
||||
const placeholder = computed(() => {
|
||||
const name = props.productType === 2 ? '西药' : '中药';
|
||||
return `输入${name}名称搜索(显示图片、供应商、规格、价格)`;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(v) => {
|
||||
if (v == null || v === undefined) {
|
||||
selectedLabel.value = '';
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function onSelect(drug: any) {
|
||||
const id = drug._drugId ?? drug.drug?.id ?? drug.drug_id;
|
||||
selectedLabel.value =
|
||||
drug._drugName || drug.drug?.drug_name || drug.drug_name || '';
|
||||
emit('update:value', id);
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
selectedLabel.value = '';
|
||||
emit('update:value', undefined);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full space-y-2">
|
||||
<DrugSearchSelect
|
||||
:type="searchType"
|
||||
:store-id="storeId"
|
||||
:placeholder="placeholder"
|
||||
@select="onSelect"
|
||||
/>
|
||||
<div
|
||||
v-if="value != null"
|
||||
class="flex items-center gap-2 text-sm text-muted-foreground"
|
||||
>
|
||||
<span>已选:{{ selectedLabel || `ID ${value}` }}</span>
|
||||
<Button type="link" size="small" class="!p-0" @click="clearSelection">
|
||||
清除
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -4,4 +4,5 @@ export type CustomComponentType =
|
||||
| 'ApiRadioGroup'
|
||||
| 'ApiSelect'
|
||||
| 'ApiTreeSelect'
|
||||
| 'IconPicker';
|
||||
| 'IconPicker'
|
||||
| 'WarehouseAdminDrugSearch';
|
||||
|
||||
@@ -188,16 +188,28 @@ const orderTypeMap = {
|
||||
<div class="mt-4">
|
||||
<h3>订单商品</h3>
|
||||
<div v-if="data.prescription_type === 1">
|
||||
<ul class="custom-list pl-6">
|
||||
<li
|
||||
<span class="mb-3 block font-medium">药品明细</span>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<div
|
||||
v-for="(item, index) in data.product_order_items"
|
||||
:key="index"
|
||||
class="custom-list-item"
|
||||
class="tcm-detail-chip w-[220px] max-w-full flex-shrink-0 rounded-lg border border-gray-200 bg-gray-50 p-3 text-sm dark:border-gray-600 dark:bg-gray-900"
|
||||
>
|
||||
【{{ item.drug.drug_number }}】 {{ item.drug_name }} (*
|
||||
{{ item.number * (item.dosage > 0 ? item.dosage : 1) }})
|
||||
</li>
|
||||
</ul>
|
||||
<div class="font-medium">
|
||||
【{{ item.drug?.drug_number }}】{{ item.drug_name }}
|
||||
</div>
|
||||
<div class="mt-1 text-gray-600 dark:text-gray-400">
|
||||
数量:*{{ item.number * (item.dosage > 0 ? item.dosage : 1) }}
|
||||
</div>
|
||||
<div class="mt-1 text-gray-600 dark:text-gray-400">
|
||||
供货价:{{
|
||||
item.buy_price != null && item.buy_price !== ''
|
||||
? `${item.buy_price} 元`
|
||||
: '—'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card
|
||||
v-for="(item, index) in data.product_order_items"
|
||||
@@ -303,34 +315,10 @@ const orderTypeMap = {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom-list {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
.tcm-detail-chip {
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 4%);
|
||||
}
|
||||
|
||||
.custom-list-item {
|
||||
background-color: rgba(64, 158, 255, 0.04);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #409eff;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
.ant-descriptions-item-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -19,34 +19,44 @@ export const gridOptions: VxeGridProps<RowType> = {
|
||||
// { type: 'checkbox', width: 60 },
|
||||
{ type: 'expand', width: 80, slots: { content: 'expand-content' } },
|
||||
{ field: 'id', align: 'left', title: 'ID', width: 100 },
|
||||
{ field: 'order_no', align: 'left', title: '订单号' },
|
||||
{ field: 'order_no', align: 'left', width: 200, title: '订单号' },
|
||||
{
|
||||
field: 'user.avatarUrl',
|
||||
title: '下单用户信息',
|
||||
slots: { default: 'avatar' },
|
||||
width: 100,
|
||||
},
|
||||
{ field: 'store.name', title: '诊所名称' },
|
||||
{ field: 'store.name', title: '诊所名称', width: 240 },
|
||||
{
|
||||
field: 'address.name',
|
||||
title: '收货人姓名',
|
||||
width: 120,
|
||||
slots: { default: 'address-name' },
|
||||
},
|
||||
{
|
||||
field: 'address.mobile',
|
||||
title: '收货人联系方式',
|
||||
width: 120,
|
||||
slots: { default: 'address-mobile' },
|
||||
},
|
||||
{
|
||||
field: 'delivery_method',
|
||||
title: '订单类型&邮寄方式&订单状态',
|
||||
width: 120,
|
||||
slots: { default: 'delivery-method' },
|
||||
},
|
||||
{ field: 'items_price', title: '药品总价' },
|
||||
{ field: 'total_pay_price', title: '支付总价' },
|
||||
{ field: 'items_price', width: 120, title: '药品总价' },
|
||||
{ field: 'total_pay_price', width: 120, title: '支付总价' },
|
||||
{
|
||||
field: 'register_price',
|
||||
title: '挂号金额',
|
||||
width: 110,
|
||||
slots: { default: 'register-fee' },
|
||||
},
|
||||
{
|
||||
field: 'is_sync_erp',
|
||||
title: 'Erp状态',
|
||||
width: 120,
|
||||
slots: { default: 'is-sync-erp' },
|
||||
},
|
||||
{
|
||||
@@ -61,11 +71,17 @@ export const gridOptions: VxeGridProps<RowType> = {
|
||||
width: 100,
|
||||
slots: { default: 'is-online' },
|
||||
},
|
||||
{ field: 'pay_time', title: '支付时间', slots: { default: 'pay-time' } },
|
||||
{ field: 'created_at', title: '下单时间' },
|
||||
{
|
||||
field: 'pay_time',
|
||||
title: '支付时间',
|
||||
width: 240,
|
||||
slots: { default: 'pay-time' },
|
||||
},
|
||||
{ field: 'created_at', width: 240, title: '下单时间' },
|
||||
{
|
||||
type: 'html',
|
||||
title: '操作',
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
width: 200,
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type { VxeGridListeners } from '#/adapter/vxe-table';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import {
|
||||
AnalysisOverview,
|
||||
@@ -31,8 +32,48 @@ import Refund from './components/refund.vue';
|
||||
import { formOptions } from './config/search';
|
||||
import { gridOptions } from './config/table';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const hasTopTableDropDownActions = ref(false);
|
||||
|
||||
function findRegisterOrderListPath(): null | string {
|
||||
const routes = router.getRoutes();
|
||||
const hit = routes.find(
|
||||
(r) =>
|
||||
typeof r.path === 'string' &&
|
||||
r.path.length > 0 &&
|
||||
!r.redirect &&
|
||||
/register/i.test(r.path) &&
|
||||
/order/i.test(r.path),
|
||||
);
|
||||
return hit?.path ?? null;
|
||||
}
|
||||
|
||||
function goRegisterOrder(row: Record<string, any>) {
|
||||
const orderNo = row.register_order_no;
|
||||
if (!orderNo) {
|
||||
message.warning('无关联挂号订单号');
|
||||
return;
|
||||
}
|
||||
const path = findRegisterOrderListPath();
|
||||
if (!path) {
|
||||
message.warning('未找到挂号订单菜单路由,请从左侧菜单进入挂号订单');
|
||||
return;
|
||||
}
|
||||
void router.push({ path, query: { order_no: String(orderNo) } });
|
||||
}
|
||||
|
||||
function formatRegisterAmount(row: Record<string, any>) {
|
||||
if (row.register_id == null || row.register_id === '') {
|
||||
return '—';
|
||||
}
|
||||
const n = Number(row.register_price);
|
||||
if (!Number.isFinite(n)) {
|
||||
return '—';
|
||||
}
|
||||
return `¥${n.toFixed(2)}`;
|
||||
}
|
||||
|
||||
const gridEvents: VxeGridListeners<any> = {
|
||||
checkboxChange() {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
@@ -119,8 +160,8 @@ const saleAmount = () => {
|
||||
overviewItems.value = [
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
title: '累计收益',
|
||||
totalTitle: '累计收益',
|
||||
title: '销售金额',
|
||||
totalTitle: '销售金额',
|
||||
totalValue: total.value,
|
||||
value: total.value,
|
||||
},
|
||||
@@ -338,6 +379,19 @@ const openOrderAmountVerify = () => {
|
||||
<template #pay-time="{ row }">
|
||||
{{ row?.pay_time || '未支付' }}
|
||||
</template>
|
||||
<template #register-fee="{ row }">
|
||||
<div class="flex flex-col items-start leading-snug">
|
||||
<Button
|
||||
v-if="row.register_order_no"
|
||||
class="!h-auto !px-0 !py-0"
|
||||
type="link"
|
||||
@click="goRegisterOrder(row)"
|
||||
>
|
||||
{{ formatRegisterAmount(row) }}
|
||||
</Button>
|
||||
<span v-else>{{ formatRegisterAmount(row) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #is-sync-erp="{ row }">
|
||||
<Tag v-if="row.is_sync_erp === 1" color="green">已同步</Tag>
|
||||
<Tag v-else color="red">未同步</Tag>
|
||||
@@ -519,19 +573,30 @@ const openOrderAmountVerify = () => {
|
||||
|
||||
<div
|
||||
v-if="row.prescription_type === 1"
|
||||
class="medication-details mt-5 p-10"
|
||||
class="medication-details mt-5 px-4 pb-4"
|
||||
>
|
||||
<span class="mb-2 font-medium">药品明细:</span>
|
||||
<ul class="custom-list pl-6">
|
||||
<li
|
||||
<span class="mb-3 block font-medium">药品明细:</span>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<div
|
||||
v-for="(item, index) in row.product_order_items"
|
||||
:key="index"
|
||||
class="custom-list-item"
|
||||
class="tcm-drug-chip w-[200px] max-w-full flex-shrink-0 rounded-lg border border-gray-200 bg-white p-3 text-sm dark:border-gray-600 dark:bg-gray-900"
|
||||
>
|
||||
【{{ item.drug.drug_number }}】 {{ item.drug_name }} (*
|
||||
{{ item.number * (item.dosage > 0 ? item.dosage : 1) }})
|
||||
</li>
|
||||
</ul>
|
||||
<div class="font-medium">
|
||||
【{{ item.drug?.drug_number }}】{{ item.drug_name }}
|
||||
</div>
|
||||
<div class="mt-1 text-gray-600 dark:text-gray-400">
|
||||
数量:*{{ item.number * (item.dosage > 0 ? item.dosage : 1) }}
|
||||
</div>
|
||||
<div class="mt-1 text-gray-600 dark:text-gray-400">
|
||||
供货价:{{
|
||||
item.buy_price != null && item.buy_price !== ''
|
||||
? `${item.buy_price} 元`
|
||||
: '—'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!row.prescription_type"></div>
|
||||
<div
|
||||
@@ -547,32 +612,7 @@ const openOrderAmountVerify = () => {
|
||||
</Page>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.custom-list {
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.custom-list-item {
|
||||
background-color: rgba(64, 158, 255, 0.04);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #409eff;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.tcm-drug-chip {
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 4%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeGridListeners } from '#/adapter/vxe-table';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { nextTick, onMounted, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import {
|
||||
Page,
|
||||
@@ -38,6 +39,20 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
gridEvents,
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
onMounted(() => {
|
||||
void nextTick(async () => {
|
||||
const raw = route.query.order_no;
|
||||
const orderNo = Array.isArray(raw) ? raw[0] : raw;
|
||||
if (!orderNo || !gridApi.formApi?.setValues) {
|
||||
return;
|
||||
}
|
||||
await gridApi.formApi.setValues({ order_no: String(orderNo) });
|
||||
await gridApi.query();
|
||||
});
|
||||
});
|
||||
|
||||
const [PrescriptionDetailModal, PrescriptionDetailModalApi] = useVbenModal({
|
||||
connectedComponent: PrescriptionDetail,
|
||||
});
|
||||
|
||||
@@ -18,6 +18,13 @@ export async function getWarehouseDrugManagementOption(data: any) {
|
||||
return requestClient.get<any>(`${prefix}option`, { params: data });
|
||||
}
|
||||
|
||||
/** 仓库筛选:商品类型(与后端 ProductTypeEnum 一致) */
|
||||
export async function getWarehouseProductTypeOptions() {
|
||||
return requestClient.get<{ label: string; value: number }[]>(
|
||||
`${prefix}product-type-options`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取西(中成)药详情
|
||||
* @param id
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
@@ -12,11 +11,11 @@ import {
|
||||
createWarehouseDrugManagement,
|
||||
updateWarehouseDrugManagement,
|
||||
} from '../api';
|
||||
import { modalFormProps } from '../config/form';
|
||||
import {getDrugUseList} from "#/views/doctor/doctor-reception/api";
|
||||
|
||||
|
||||
const userStore = useUserStore();
|
||||
import {
|
||||
modalFormProps,
|
||||
warehouseAdminDrugIdApiSelectProps,
|
||||
} from '../config/form';
|
||||
import { getDrugUseList } from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
const drugTime = ref([]);
|
||||
const drugType = ref([]);
|
||||
@@ -86,8 +85,35 @@ const [Modal, modalApi] = useVbenModal({
|
||||
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;
|
||||
|
||||
const drugIdSchemaPatch =
|
||||
listDrugType === 5
|
||||
? {
|
||||
fieldName: 'drug_id',
|
||||
component: 'ApiSelect' as const,
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
...warehouseAdminDrugIdApiSelectProps,
|
||||
},
|
||||
}
|
||||
: {
|
||||
fieldName: 'drug_id',
|
||||
component: 'WarehouseAdminDrugSearch' as const,
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
productType: listDrugType,
|
||||
},
|
||||
};
|
||||
|
||||
formApi.updateSchema([
|
||||
drugIdSchemaPatch,
|
||||
{
|
||||
componentProps: {
|
||||
options: drugTime.value,
|
||||
@@ -113,10 +139,9 @@ const [Modal, modalApi] = useVbenModal({
|
||||
fieldName: 'frequency_id',
|
||||
},
|
||||
]);
|
||||
const { values, update } = modalApi.getData<Record<string, any>>();
|
||||
const { values, update } = modalData ?? {};
|
||||
if (values) {
|
||||
console.log(values, 'sssssssssssss')
|
||||
isUpdate.value = update;
|
||||
isUpdate.value = !!update;
|
||||
formApi.setValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
import type {VbenFormProps} from '#/adapter/form';
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
|
||||
import {getSupplierOption} from '#/views/system/supplier/api';
|
||||
import {
|
||||
getWarehouseDrugManagementOption
|
||||
} from "#/views/business/warehouse-drug-management/admin/api";
|
||||
getWarehouseDrugManagementOption,
|
||||
} from '#/views/business/warehouse-drug-management/admin/api';
|
||||
|
||||
/** 服务包等场景:总仓 option 下拉(与原先 ApiSelect 一致) */
|
||||
export const warehouseAdminDrugIdApiSelectProps = {
|
||||
api: getWarehouseDrugManagementOption,
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
filterOption: (input: string, option: any) => {
|
||||
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
},
|
||||
afterFetch: (data: { drug_name: string; id: number }[]) => {
|
||||
return data.map((item: any) => ({
|
||||
label: `${item.drug_name}【${item.pinyin_simple}】`,
|
||||
value: item.id,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
export const modalFormProps: VbenFormProps = {
|
||||
wrapperClass: 'grid-cols-12', // 24栅格,
|
||||
@@ -29,26 +43,13 @@ export const modalFormProps: VbenFormProps = {
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'ApiSelect',
|
||||
component: 'WarehouseAdminDrugSearch',
|
||||
fieldName: 'drug_id',
|
||||
label: '选择加入的药品',
|
||||
formItemClass: 'col-span-12',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
api: getWarehouseDrugManagementOption,
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
filterOption: (input: string, option: any) => {
|
||||
// 自定义过滤逻辑,确保可以根据 name 进行搜索
|
||||
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
},
|
||||
// 菜单接口转options格式
|
||||
afterFetch: (data: { drug_name: string; id: number }[]) => {
|
||||
return data.map((item: any) => ({
|
||||
label: `${item.drug_name}【${item.pinyin_simple}】`,
|
||||
value: item.id,
|
||||
}));
|
||||
},
|
||||
productType: 1,
|
||||
},
|
||||
dependencies: {
|
||||
show(formValues: any) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
|
||||
import { getWarehouseProductTypeOptions } from '../api';
|
||||
|
||||
// import dayjs from 'dayjs';
|
||||
|
||||
export const formOptions: VbenFormProps = {
|
||||
@@ -16,28 +18,23 @@ export const formOptions: VbenFormProps = {
|
||||
label: '药名称',
|
||||
},
|
||||
{
|
||||
component: 'VbenSelect',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '输入名称',
|
||||
options: [
|
||||
{
|
||||
label: '中药',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '西药',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '服务包',
|
||||
value: 5,
|
||||
},
|
||||
],
|
||||
allowClear: true,
|
||||
api: getWarehouseProductTypeOptions,
|
||||
placeholder: '请选择类型',
|
||||
afterFetch: (data: { label: string; value: number }[]) => {
|
||||
if (!Array.isArray(data)) return [];
|
||||
return data.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}));
|
||||
},
|
||||
},
|
||||
defaultValue: 1,
|
||||
fieldName: 'type',
|
||||
label: '类型',
|
||||
}
|
||||
},
|
||||
// {
|
||||
// component: 'RangePicker',
|
||||
// componentProps: {
|
||||
|
||||
@@ -50,12 +50,24 @@ const [ExcelUploadModal, ExcelUploadModalApi] = useVbenModal({
|
||||
connectedComponent: ExcelUpload,
|
||||
});
|
||||
|
||||
const TCM_PLACEHOLDER_IMAGE =
|
||||
'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250510/cd470ebf97e31c4048ba502ba71b66a3.jpg';
|
||||
|
||||
function rowProductImageSrc(row: any) {
|
||||
const drug = row?.drug;
|
||||
const isChinese = drug?.type === 1;
|
||||
if (isChinese) {
|
||||
return drug?.image || TCM_PLACEHOLDER_IMAGE;
|
||||
}
|
||||
return drug?.image || '';
|
||||
}
|
||||
|
||||
const showModal = (data = {}, isUpdate = false) => {
|
||||
formModalApi.setData({
|
||||
// 表单值
|
||||
values: data,
|
||||
update: isUpdate,
|
||||
gridApi,
|
||||
listDrugType: gridApi.formApi.latestSubmissionValues?.type,
|
||||
});
|
||||
formModalApi.open();
|
||||
};
|
||||
@@ -165,10 +177,8 @@ const updateStatus = (id: number) => {
|
||||
</template>
|
||||
<template #image="{ row }">
|
||||
<Image
|
||||
:src="
|
||||
row.drug?.image ||
|
||||
'https://xiaokang88.oss-cn-hangzhou.aliyuncs.com/xk_upload_20250510/cd470ebf97e31c4048ba502ba71b66a3.jpg'
|
||||
"
|
||||
v-if="rowProductImageSrc(row)"
|
||||
:src="rowProductImageSrc(row)"
|
||||
height="30"
|
||||
width="30"
|
||||
/>
|
||||
|
||||
@@ -18,6 +18,13 @@ export async function getWarehouseDrugManagementStoreOption(data: any) {
|
||||
return requestClient.get<any>(`${prefix}option`, { params: data });
|
||||
}
|
||||
|
||||
/** 仓库筛选:商品类型(与后端 ProductTypeEnum 一致) */
|
||||
export async function getWarehouseProductTypeOptions() {
|
||||
return requestClient.get<{ label: string; value: number }[]>(
|
||||
`${prefix}product-type-options`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取诊所仓库商品药详情
|
||||
* @param id
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
|
||||
import { getWarehouseProductTypeOptions } from '../api';
|
||||
|
||||
// import dayjs from 'dayjs';
|
||||
|
||||
export const formOptions: VbenFormProps = {
|
||||
@@ -16,26 +18,19 @@ export const formOptions: VbenFormProps = {
|
||||
label: '药名称',
|
||||
},
|
||||
{
|
||||
component: 'VbenSelect',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '输入名称',
|
||||
options: [
|
||||
{
|
||||
label: '中药',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '西药',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '服务包',
|
||||
value: 5,
|
||||
},
|
||||
],
|
||||
show: () => {
|
||||
return false;
|
||||
allowClear: true,
|
||||
api: getWarehouseProductTypeOptions,
|
||||
placeholder: '请选择类型',
|
||||
afterFetch: (data: { label: string; value: number }[]) => {
|
||||
if (!Array.isArray(data)) return [];
|
||||
return data.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}));
|
||||
},
|
||||
show: () => false,
|
||||
},
|
||||
disabled: true,
|
||||
defaultValue: 2,
|
||||
|
||||
@@ -18,6 +18,13 @@ export async function getWarehouseDrugManagementStoreOption(data: any) {
|
||||
return requestClient.get<any>(`${prefix}option`, { params: data });
|
||||
}
|
||||
|
||||
/** 仓库筛选:商品类型(与后端 ProductTypeEnum 一致) */
|
||||
export async function getWarehouseProductTypeOptions() {
|
||||
return requestClient.get<{ label: string; value: number }[]>(
|
||||
`${prefix}product-type-options`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取诊所仓库商品药详情
|
||||
* @param id
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
|
||||
import { getWarehouseProductTypeOptions } from '../api';
|
||||
|
||||
// import dayjs from 'dayjs';
|
||||
|
||||
export const formOptions: VbenFormProps = {
|
||||
@@ -16,28 +18,23 @@ export const formOptions: VbenFormProps = {
|
||||
label: '药名称',
|
||||
},
|
||||
{
|
||||
component: 'VbenSelect',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
placeholder: '输入名称',
|
||||
options: [
|
||||
{
|
||||
label: '中药',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '西药',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '服务包',
|
||||
value: 5,
|
||||
},
|
||||
],
|
||||
allowClear: true,
|
||||
api: getWarehouseProductTypeOptions,
|
||||
placeholder: '请选择类型',
|
||||
afterFetch: (data: { label: string; value: number }[]) => {
|
||||
if (!Array.isArray(data)) return [];
|
||||
return data.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}));
|
||||
},
|
||||
},
|
||||
defaultValue: 1,
|
||||
fieldName: 'type',
|
||||
label: '类型',
|
||||
}
|
||||
},
|
||||
// {
|
||||
// component: 'RangePicker',
|
||||
// componentProps: {
|
||||
|
||||
@@ -86,6 +86,17 @@ export async function receptionApi(id: any) {
|
||||
return requestClient.post<any>(`${prefix}reception`, { id });
|
||||
}
|
||||
|
||||
/**
|
||||
* 医生撤回处方(仅待支付订单)
|
||||
*/
|
||||
export async function withdrawPrescriptionApi(data: {
|
||||
prescription_id: number;
|
||||
register_id: number;
|
||||
reason?: string;
|
||||
}) {
|
||||
return requestClient.post<any>(`${prefix}withdraw-prescription`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接诊
|
||||
*/
|
||||
@@ -301,6 +312,16 @@ export async function saveChineseCommonPrescriptionApi(data: {
|
||||
dosage?: number;
|
||||
/** 每日次数(可选,默认2) */
|
||||
day_dosage?: number;
|
||||
/** 1=自制剂 2=委托调剂(与后端 saveChinese 一致) */
|
||||
rule_type?: number;
|
||||
/** 包法ID(自制剂时) */
|
||||
package_method_id?: number;
|
||||
/** 制剂规则ID(委托调剂时) */
|
||||
process_rule_id?: number;
|
||||
/** 煎法子规则ID(委托调剂时) */
|
||||
child_process_rule_id?: number;
|
||||
/** 规格/备注规则ID(委托调剂时) */
|
||||
process_rule_note_id?: number;
|
||||
}) {
|
||||
return requestClient.post<any>(
|
||||
`${commonPrescriptionPrefix}save-chinese`,
|
||||
|
||||
@@ -166,7 +166,7 @@ const getImageSource = (imageString) => {
|
||||
</div>
|
||||
<div class="preparation-info">
|
||||
<p>
|
||||
用法:每日{{ recipe.deployment }}次,共{{ recipe.dosage }}剂
|
||||
用法:每日{{ recipe.consumption }}次,共{{ recipe.dosage }}剂
|
||||
</p>
|
||||
<p>
|
||||
使用方式:{{
|
||||
|
||||
@@ -43,10 +43,12 @@ import {
|
||||
getCurrentStoreTypeApi,
|
||||
getPatientItem,
|
||||
getPatientList,
|
||||
getPrescriptionInfoApi,
|
||||
getProcessRuleList,
|
||||
getProductListDoctorReception,
|
||||
receptionApi,
|
||||
switchStoreApi,
|
||||
withdrawPrescriptionApi,
|
||||
} from '#/views/doctor/doctor-reception/api';
|
||||
|
||||
import DiagnosisModal from './components/DiagnosisModal.vue';
|
||||
@@ -181,6 +183,10 @@ const activePatient = ref<null | Patient>(null);
|
||||
const patientInfo = ref<null | Patient>(null);
|
||||
const userPatientHealthInquiry = ref<null | UserPatientHealthInquiry>(null);
|
||||
const activeCategory = ref(2);
|
||||
/** 仅中药/西药支持常用方模板(保健食品不提供常用方) */
|
||||
const canUseCommonPrescription = computed(() =>
|
||||
[1, 2].includes(activeCategory.value),
|
||||
);
|
||||
const diagnosis = ref('');
|
||||
const medicalAdvice = ref('');
|
||||
const treatmentPrice = ref(0);
|
||||
@@ -746,57 +752,39 @@ function openCommonPrescriptionModal() {
|
||||
async function handleSelectCommonPrescription(data: any, type: number) {
|
||||
const { prescription, recipes } = data;
|
||||
|
||||
// 根据类型处理不同的药品数据
|
||||
// 根据类型处理药品:整表覆盖为常用方内容,不与当前处方按药品 ID 去重合并
|
||||
if (type === 1) {
|
||||
// 西药处方
|
||||
recipes.forEach((recipe: any) => {
|
||||
const newProduct = {
|
||||
index_id: recipe.id,
|
||||
id: recipe.drug_id, // 使用药品主表ID
|
||||
drug_name: recipe.drug_name,
|
||||
number: recipe.number || 1,
|
||||
use_num: recipe.use_time,
|
||||
use_type: recipe.use_type,
|
||||
use_frequency: recipe.use_frequency,
|
||||
unit: recipe.west_unit,
|
||||
price: recipe.price || 0,
|
||||
time_id: recipe.time_id,
|
||||
type_id: recipe.type_id,
|
||||
frequency_id: recipe.frequency_id,
|
||||
unit_id: recipe.unit_id,
|
||||
image: recipe.image,
|
||||
instruction: recipe.instruction,
|
||||
type: recipe.type,
|
||||
select_number: 1,
|
||||
};
|
||||
// 检查是否已存在 - 使用药品主表ID比对
|
||||
const existItem = currentDrugs.value.find(
|
||||
(item) => item.id === recipe.drug_id,
|
||||
);
|
||||
if (!existItem) {
|
||||
currentDrugs.value.push(newProduct);
|
||||
}
|
||||
});
|
||||
currentDrugs.value = (recipes || []).map((recipe: any) => ({
|
||||
index_id: recipe.id,
|
||||
id: recipe.drug_id,
|
||||
drug_name: recipe.drug_name,
|
||||
number: recipe.number || 1,
|
||||
use_num: recipe.use_time,
|
||||
use_type: recipe.use_type,
|
||||
use_frequency: recipe.use_frequency,
|
||||
unit: recipe.west_unit,
|
||||
price: recipe.price || 0,
|
||||
time_id: recipe.time_id,
|
||||
type_id: recipe.type_id,
|
||||
frequency_id: recipe.frequency_id,
|
||||
unit_id: recipe.unit_id,
|
||||
image: recipe.image,
|
||||
instruction: recipe.instruction,
|
||||
type: recipe.type,
|
||||
select_number: recipe.select_number ?? 1,
|
||||
}));
|
||||
} else {
|
||||
// 中药/颗粒药处方 - 使用 drug_id 作为唯一标识(药品主表ID)
|
||||
recipes.forEach((recipe: any) => {
|
||||
const drugId = recipe.drug_id || recipe.id; // 药品主表ID
|
||||
const newProduct = {
|
||||
index_id: recipe.id, // 处方明细ID
|
||||
id: drugId, // 药品主表ID
|
||||
currentDrugs.value = (recipes || []).map((recipe: any) => {
|
||||
const drugId = recipe.drug_id || recipe.id;
|
||||
return {
|
||||
index_id: recipe.id,
|
||||
id: drugId,
|
||||
drug_name: recipe.drug_name || recipe.name,
|
||||
number: recipe.number || 1,
|
||||
price: recipe.price || 0,
|
||||
way_id: recipe.way_id || 0,
|
||||
select_number: 1,
|
||||
select_number: recipe.select_number ?? 1,
|
||||
};
|
||||
// 检查是否已存在 - 使用药品主表ID比对
|
||||
const existItem = currentDrugs.value.find(
|
||||
(item) => item.id === drugId,
|
||||
);
|
||||
if (!existItem) {
|
||||
currentDrugs.value.push(newProduct);
|
||||
}
|
||||
});
|
||||
|
||||
// 写入规则类型和剂量信息(中药)- 使用 ?? 运算符提供默认值
|
||||
@@ -868,6 +856,11 @@ const saveAsCommonPrescription = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![1, 2].includes(activeCategory.value)) {
|
||||
message.warning('当前分类不支持保存为常用方');
|
||||
return;
|
||||
}
|
||||
|
||||
isSavingCommonPrescription.value = true;
|
||||
|
||||
try {
|
||||
@@ -1135,6 +1128,274 @@ async function loadProcessRuleData(pid = 0, ruleId = 0) {
|
||||
}
|
||||
}
|
||||
|
||||
function parsePrescriptionDetailContent(detail: any): Record<string, any> {
|
||||
let c = detail?.content;
|
||||
if (typeof c === 'string') {
|
||||
try {
|
||||
c = JSON.parse(c);
|
||||
} catch {
|
||||
c = {};
|
||||
}
|
||||
}
|
||||
return c && typeof c === 'object' ? c : {};
|
||||
}
|
||||
|
||||
function mapWestRepiceToProduct(recipe: any): Record<string, any> | null {
|
||||
if (!recipe) return null;
|
||||
let drugObj: Record<string, any> = {};
|
||||
try {
|
||||
const raw = recipe.content;
|
||||
drugObj = typeof raw === 'string' ? JSON.parse(raw) : (raw || {});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const drugId = Number(drugObj.id);
|
||||
if (!drugId) return null;
|
||||
|
||||
const selectNumber = Math.max(
|
||||
1,
|
||||
Number(recipe.number ?? drugObj.select_number ?? drugObj.number ?? 1) || 1,
|
||||
);
|
||||
const grainNumber = Math.max(
|
||||
1,
|
||||
Number(
|
||||
drugObj.number ?? drugObj.grain_number ?? recipe.grain_number ?? 1,
|
||||
) || 1,
|
||||
);
|
||||
|
||||
const timeId = recipe.time_id ?? drugObj.time_id;
|
||||
const typeId = recipe.type_id ?? drugObj.type_id;
|
||||
const freqId = recipe.f_id ?? drugObj.frequency_id;
|
||||
const unitId = recipe.wu_id ?? drugObj.unit_id;
|
||||
|
||||
return {
|
||||
index_id: drugId,
|
||||
id: drugId,
|
||||
drug_name: drugObj.drug_name || drugObj.name || '',
|
||||
number: grainNumber,
|
||||
use_num: drugTime.value.find((item: any) => item.id === timeId),
|
||||
use_type: drugUseType.value.find((item: any) => item.id === typeId),
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(item: any) => item.id === freqId,
|
||||
),
|
||||
unit: drugUnit.value.find((item: any) => item.id === unitId),
|
||||
price: Number(drugObj.sell_price ?? drugObj.price ?? 0),
|
||||
way_id: drugObj.way_id,
|
||||
use_ways: drugUseWay.value.find((item: any) => item.id === drugObj.way_id),
|
||||
time_id: timeId,
|
||||
type_id: typeId,
|
||||
frequency_id: freqId,
|
||||
unit_id: unitId,
|
||||
image: drugObj.image || '',
|
||||
instruction: drugObj.instruction || '',
|
||||
type: drugObj.type,
|
||||
select_number: selectNumber,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将历史处方详情写入当前接诊编辑区(不提交)
|
||||
*/
|
||||
async function applyHistoricalPrescriptionDetail(detail: any): Promise<boolean> {
|
||||
const content = parsePrescriptionDetailContent(detail);
|
||||
const pt = Number(detail.prescription_type);
|
||||
const supported = [1, 2, 3, 5, 6, 7];
|
||||
if (!supported.includes(pt)) {
|
||||
message.warning('该处方类型暂不支持复用');
|
||||
return false;
|
||||
}
|
||||
|
||||
const pid = activePatient.value?.id;
|
||||
if (!pid) {
|
||||
message.warning('请先选择患者');
|
||||
return false;
|
||||
}
|
||||
|
||||
localStorage.setItem(
|
||||
getStorageKey(pid, activeCategory.value),
|
||||
JSON.stringify(currentDrugs.value),
|
||||
);
|
||||
|
||||
activeCategory.value = pt;
|
||||
localStorage.setItem(`activeCategory${pid}`, String(pt));
|
||||
|
||||
const repiceList = Array.isArray(content.repice) ? content.repice : [];
|
||||
|
||||
if (pt === 1) {
|
||||
const rep0 = repiceList[0];
|
||||
if (!rep0) {
|
||||
message.warning('处方中药品数据为空');
|
||||
return false;
|
||||
}
|
||||
let rows: any[] = [];
|
||||
try {
|
||||
const raw = rep0.content;
|
||||
rows = typeof raw === 'string' ? JSON.parse(raw) : (raw || []);
|
||||
} catch {
|
||||
message.error('解析中药处方失败');
|
||||
return false;
|
||||
}
|
||||
if (!Array.isArray(rows) || rows.length === 0) {
|
||||
message.warning('处方中药品数据为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
currentDrugs.value = rows.map((d: any) => {
|
||||
const drugId = Number(d.drug_id ?? d.id);
|
||||
// 落库煎法在 ChineseMedicineModel.order,详情可能带 use_way / useWay
|
||||
const wayId =
|
||||
d.way_id ?? d.use_way?.id ?? d.useWay?.id ?? d.order;
|
||||
return {
|
||||
index_id: d.id ?? drugId,
|
||||
id: drugId,
|
||||
drug_name: d.name || d.drug_name || '',
|
||||
number: d.number ?? 1,
|
||||
price: Number(d.price ?? 0),
|
||||
way_id: wayId,
|
||||
use_ways:
|
||||
drugUseWay.value.find((item: any) => item.id === wayId) ??
|
||||
d.use_way ??
|
||||
d.useWay,
|
||||
use_num: drugTime.value.find((item: any) => item.id === d.time_id),
|
||||
use_type: drugUseType.value.find((item: any) => item.id === d.type_id),
|
||||
use_frequency: drugUseFrequency.value.find(
|
||||
(item: any) => item.id === d.frequency_id,
|
||||
),
|
||||
unit: drugUnit.value.find((item: any) => item.id === d.unit_id),
|
||||
time_id: d.time_id,
|
||||
type_id: d.type_id,
|
||||
frequency_id: d.frequency_id,
|
||||
unit_id: d.unit_id,
|
||||
image: d.image || '',
|
||||
instruction: d.instruction || '',
|
||||
type: d.type,
|
||||
};
|
||||
});
|
||||
|
||||
ruleType.value = rep0.deployment === 2 ? 1 : 2;
|
||||
dosage.value = Number(rep0.dosage ?? 7);
|
||||
dayDosage.value = Number(rep0.consumption ?? 2);
|
||||
packageMethodId.value = rep0.package_method_id ?? 2;
|
||||
|
||||
if (rep0.process_rule_id && Number(rep0.process_rule_id) > 0) {
|
||||
processRuleId.value = rep0.process_rule_id;
|
||||
await loadProcessRuleData(rep0.process_rule_id, 0);
|
||||
}
|
||||
if (rep0.child_process_rule_id && Number(rep0.child_process_rule_id) > 0) {
|
||||
childProcessRuleId.value = rep0.child_process_rule_id;
|
||||
await loadProcessRuleData(0, rep0.child_process_rule_id);
|
||||
}
|
||||
if (rep0.process_rule_note_id && Number(rep0.process_rule_note_id) > 0) {
|
||||
processRuleNoteId.value = rep0.process_rule_note_id;
|
||||
}
|
||||
|
||||
if (!rep0.process_rule_id) {
|
||||
message.warning('历史处方未包含完整加工规则,请核对包法/制剂与规格');
|
||||
}
|
||||
} else {
|
||||
const mapped: any[] = [];
|
||||
for (const recipe of repiceList) {
|
||||
const row = mapWestRepiceToProduct(recipe);
|
||||
if (row) mapped.push(row);
|
||||
}
|
||||
if (mapped.length === 0) {
|
||||
message.warning('未解析到可复用的药品行');
|
||||
return false;
|
||||
}
|
||||
currentDrugs.value = mapped;
|
||||
}
|
||||
|
||||
diagnosis.value =
|
||||
detail.clinical_diagnose ?? content.clinical_diagnose ?? '';
|
||||
medicalAdvice.value = detail.doctor_order ?? content.doctor_order ?? '';
|
||||
category.value =
|
||||
detail.category === 2 || detail.category === '2' || content.category === '医保'
|
||||
? 2
|
||||
: 1;
|
||||
doctorSecondSign.value = 0;
|
||||
processRulePrice.value = 0;
|
||||
|
||||
localStorage.setItem(
|
||||
getStorageKey(pid, activeCategory.value),
|
||||
JSON.stringify(currentDrugs.value),
|
||||
);
|
||||
if (activePatient.value?.id) {
|
||||
localStorage.setItem(
|
||||
`activeCategory${activePatient.value.id}`,
|
||||
String(activeCategory.value),
|
||||
);
|
||||
}
|
||||
getCurrentDrugs();
|
||||
return true;
|
||||
}
|
||||
|
||||
async function reusePrescriptionFromList(prescriptionId: number) {
|
||||
if (!activePatient.value?.id) {
|
||||
message.warning('请先选择患者');
|
||||
return;
|
||||
}
|
||||
const draft = currentDrugs.value;
|
||||
const go = async () => {
|
||||
try {
|
||||
const detail = await getPrescriptionInfoApi(prescriptionId);
|
||||
const ok = await applyHistoricalPrescriptionDetail(detail);
|
||||
if (ok) {
|
||||
tabType.value = 2;
|
||||
message.success('已复用到开方页,请核对后发送');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
message.error('加载处方详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
if (draft.length > 0) {
|
||||
AntModal.confirm({
|
||||
title: '覆盖当前草稿?',
|
||||
content: '当前处方清单中有药品,复用将替换为所选历史处方内容。',
|
||||
onOk: go,
|
||||
});
|
||||
} else {
|
||||
await go();
|
||||
}
|
||||
}
|
||||
|
||||
function confirmWithdrawPrescription(item: any) {
|
||||
const registerId = Number.parseInt(
|
||||
localStorage.getItem(`doctorReception-id`) || '0',
|
||||
10,
|
||||
);
|
||||
if (!registerId || !item?.id) {
|
||||
message.error('参数错误');
|
||||
return;
|
||||
}
|
||||
AntModal.confirm({
|
||||
title: '确认撤回处方?',
|
||||
content:
|
||||
'撤回后患者将无法继续支付该订单,库存占用将释放。此操作不可撤销。',
|
||||
async onOk() {
|
||||
try {
|
||||
await withdrawPrescriptionApi({
|
||||
prescription_id: item.id,
|
||||
register_id: registerId,
|
||||
});
|
||||
message.success('处方已撤回');
|
||||
if (selectPatientId.value) {
|
||||
const value = await getPatientItem(selectPatientId.value);
|
||||
patientInfo.value = value;
|
||||
userPatientHealthInquiry.value = value.user_patient_health_inquiry;
|
||||
prescriptionList.value = value.prescription;
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(
|
||||
e?.response?.data?.message || e?.message || '撤回失败',
|
||||
);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
loadProcessRuleData();
|
||||
|
||||
/**
|
||||
@@ -1831,6 +2092,17 @@ watch(
|
||||
<Button type="link" @click="openPrescriptionDetail(item.id)">
|
||||
查看处方
|
||||
</Button>
|
||||
<Button
|
||||
v-if="item.can_withdraw"
|
||||
danger
|
||||
type="link"
|
||||
@click="confirmWithdrawPrescription(item)"
|
||||
>
|
||||
撤回
|
||||
</Button>
|
||||
<Button type="link" @click="reusePrescriptionFromList(item.id)">
|
||||
复用
|
||||
</Button>
|
||||
<span class="time-line-item-created">{{
|
||||
item.created_at
|
||||
}}</span>
|
||||
@@ -1847,11 +2119,22 @@ watch(
|
||||
{{ item.category === 1 ? '自费' : '医保' }}
|
||||
</Tag>
|
||||
|
||||
<Tag v-if="item.status === 0" color="warning">待审核</Tag>
|
||||
<Tag v-else-if="item.status === 1" color="success">已通过</Tag>
|
||||
<Tag v-else-if="item.status === 2" color="error">未通过</Tag>
|
||||
<Tag v-else-if="item.status === 3" color="#455cda">无需审核</Tag>
|
||||
<Tag v-else-if="item.status === 4" color="#455cda">无需审核</Tag>
|
||||
<template v-if="Number(item.cancel_status) === 1">
|
||||
<Tag color="default">
|
||||
{{
|
||||
String(item.cancel_remark || '').includes('医生撤回')
|
||||
? '已撤回'
|
||||
: '已取消'
|
||||
}}
|
||||
</Tag>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Tag v-if="item.status === 0" color="warning">待审核</Tag>
|
||||
<Tag v-else-if="item.status === 1" color="success">已通过</Tag>
|
||||
<Tag v-else-if="item.status === 2" color="error">未通过</Tag>
|
||||
<Tag v-else-if="item.status === 3" color="#455cda">无需审核</Tag>
|
||||
<Tag v-else-if="item.status === 4" color="#455cda">无需审核</Tag>
|
||||
</template>
|
||||
<Tag color="#455cda">处方ID:{{ item.id }}</Tag>
|
||||
<!-- <Tag class="ml-5">-->
|
||||
<!-- {{ item.category === 1 ? '自费' : '医保' }}-->
|
||||
@@ -1901,8 +2184,9 @@ watch(
|
||||
>
|
||||
添加商品
|
||||
</Button>
|
||||
<!-- 常用方按钮 -->
|
||||
<!-- 常用方按钮(产品服务包/非药品/医疗器械无模板接口) -->
|
||||
<Button
|
||||
v-if="canUseCommonPrescription"
|
||||
type="primary"
|
||||
:class="activeCategory !== 1 ? 'ml-3' : ''"
|
||||
@click="openCommonPrescriptionModal"
|
||||
@@ -1910,6 +2194,7 @@ watch(
|
||||
选择常用方
|
||||
</Button>
|
||||
<Button
|
||||
v-if="canUseCommonPrescription"
|
||||
type="default"
|
||||
class="ml-3"
|
||||
@click="openSaveCommonPrescriptionModal"
|
||||
|
||||
@@ -55,7 +55,7 @@ const fetchPatientDetail = async () => {
|
||||
// 确保正确解析返回数据
|
||||
const data = res?.result || res?.data || res || null;
|
||||
patientDetail.value = data;
|
||||
|
||||
|
||||
// 检查是否有转诊挂号
|
||||
if (data?.register_order?.is_from_transfer === 1 && registerId) {
|
||||
await fetchTransferPrescription(registerId);
|
||||
@@ -74,7 +74,7 @@ const fetchPatientDetail = async () => {
|
||||
// 获取转诊信息
|
||||
const fetchTransferPrescription = async (registerId) => {
|
||||
if (!registerId) return;
|
||||
|
||||
|
||||
loadingTransfer.value = true;
|
||||
try {
|
||||
const res = await getTransferPrescriptionByRegisterApi(registerId);
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const prefix = 'reconciliation/';
|
||||
|
||||
function formatSearchTimeParam(st: unknown): [string, string] | undefined {
|
||||
if (!Array.isArray(st) || st.length < 2) return undefined;
|
||||
const fmt = (v: unknown) => {
|
||||
if (v && typeof v === 'object' && 'format' in v && typeof (v as { format: (s: string) => string }).format === 'function') {
|
||||
return (v as dayjs.Dayjs).format('YYYY-MM-DD');
|
||||
}
|
||||
const d = dayjs(v as string | number);
|
||||
return d.isValid() ? d.format('YYYY-MM-DD') : '';
|
||||
};
|
||||
const a = fmt(st[0]);
|
||||
const b = fmt(st[1]);
|
||||
if (!a || !b) return undefined;
|
||||
return [a, b];
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊所对账单
|
||||
* @param data
|
||||
@@ -8,10 +26,64 @@ const prefix = 'reconciliation/';
|
||||
export async function getReconciliationStoreList(data: any) {
|
||||
return requestClient.get<any>(`${prefix}list`, { params: data });
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊所对账单
|
||||
* @param data
|
||||
* 药品对账列表
|
||||
*/
|
||||
export async function getReconciliationDrugList(data: any) {
|
||||
return requestClient.get<any>(`${prefix}drug-list`, { params: data });
|
||||
}
|
||||
|
||||
export async function getReconciliationDrugOptions(params: {
|
||||
q?: string;
|
||||
store_id?: number;
|
||||
search_time: [string, string];
|
||||
limit?: number;
|
||||
}) {
|
||||
return requestClient.get<{ items: { value: number; label: string }[] }>(`${prefix}drug-options`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getReconciliationOrderOptions(params: {
|
||||
q?: string;
|
||||
store_id: number;
|
||||
search_time: [string, string];
|
||||
limit?: number;
|
||||
}) {
|
||||
return requestClient.get<{ items: { value: number; label: string }[] }>(`${prefix}order-options`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对账明细 Excel(药品维度 + 订单维度)
|
||||
*/
|
||||
export async function exportReconciliationExcelApi(params: {
|
||||
store_id: number;
|
||||
search_time: unknown;
|
||||
drug_id?: number;
|
||||
order_id?: number;
|
||||
by_order?: 0 | 1;
|
||||
}) {
|
||||
const search_time = formatSearchTimeParam(params.search_time);
|
||||
if (!search_time) {
|
||||
return Promise.reject(new Error('时间范围无效'));
|
||||
}
|
||||
const query: Record<string, unknown> = {
|
||||
store_id: params.store_id,
|
||||
search_time,
|
||||
};
|
||||
if (params.drug_id != null && params.drug_id > 0) {
|
||||
query.drug_id = params.drug_id;
|
||||
}
|
||||
if (params.order_id != null && params.order_id > 0) {
|
||||
query.order_id = params.order_id;
|
||||
}
|
||||
if (params.by_order === 1) {
|
||||
query.by_order = 1;
|
||||
}
|
||||
return requestClient.download(`${prefix}export-excel`, {
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
message,
|
||||
Select,
|
||||
Space,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { downloadByData } from '#/util/tool';
|
||||
|
||||
import {
|
||||
exportReconciliationExcelApi,
|
||||
getReconciliationDrugOptions,
|
||||
getReconciliationOrderOptions,
|
||||
} from '#/views/finance/reconciliation/api';
|
||||
|
||||
import { loadPageSearchTimeRange } from '#/views/finance/reconciliation/utils/formCache';
|
||||
|
||||
import { getStoreOption } from '#/views/system/store/api';
|
||||
|
||||
defineOptions({
|
||||
name: 'ReconciliationExportModal',
|
||||
});
|
||||
|
||||
type SelectLabeled = { value: number; label: string };
|
||||
|
||||
const storeId = ref<number | undefined>(undefined);
|
||||
|
||||
const storeLabel = ref('');
|
||||
|
||||
const searchTime = ref<[Dayjs, Dayjs]>([dayjs().startOf('month'), dayjs()]);
|
||||
|
||||
const storeOptions = ref<{ label: string; value: number }[]>([]);
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const fetchingStores = ref(false);
|
||||
|
||||
const drugSelect = ref<SelectLabeled | undefined>(undefined);
|
||||
const orderSelect = ref<SelectLabeled | undefined>(undefined);
|
||||
const drugSelectOptions = ref<SelectLabeled[]>([]);
|
||||
const orderSelectOptions = ref<SelectLabeled[]>([]);
|
||||
const byOrder = ref(false);
|
||||
|
||||
/** 诊所账号:仅允许导出本店,隐藏诊所选择 */
|
||||
const storeExportLocked = ref(false);
|
||||
|
||||
function searchTimeRange(): [string, string] {
|
||||
return [searchTime.value[0].format('YYYY-MM-DD'), searchTime.value[1].format('YYYY-MM-DD')];
|
||||
}
|
||||
|
||||
const searchDrugOptions = debounce(async (raw: string) => {
|
||||
if (!storeId.value) {
|
||||
drugSelectOptions.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await getReconciliationDrugOptions({
|
||||
q: raw,
|
||||
store_id: storeId.value,
|
||||
search_time: searchTimeRange(),
|
||||
});
|
||||
drugSelectOptions.value = (res.items || []).map((i) => ({ label: i.label, value: i.value }));
|
||||
} catch {
|
||||
drugSelectOptions.value = [];
|
||||
}
|
||||
}, 300);
|
||||
|
||||
const searchOrderOptions = debounce(async (raw: string) => {
|
||||
if (!storeId.value) {
|
||||
orderSelectOptions.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await getReconciliationOrderOptions({
|
||||
q: raw,
|
||||
store_id: storeId.value,
|
||||
search_time: searchTimeRange(),
|
||||
});
|
||||
orderSelectOptions.value = (res.items || []).map((i) => ({ label: i.label, value: i.value }));
|
||||
} catch {
|
||||
orderSelectOptions.value = [];
|
||||
}
|
||||
}, 300);
|
||||
|
||||
watch(storeId, (id) => {
|
||||
if (storeExportLocked.value) {
|
||||
return;
|
||||
}
|
||||
const o = storeOptions.value.find((x) => x.value === id);
|
||||
storeLabel.value = o?.label ?? '';
|
||||
orderSelect.value = undefined;
|
||||
orderSelectOptions.value = [];
|
||||
});
|
||||
|
||||
const loadStores = () => {
|
||||
fetchingStores.value = true;
|
||||
getStoreOption({})
|
||||
.then((res: any[]) => {
|
||||
storeOptions.value = (res || []).map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
}));
|
||||
})
|
||||
.finally(() => {
|
||||
fetchingStores.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
function popupContainerBody() {
|
||||
return document.body;
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
draggable: true,
|
||||
onCancel() {
|
||||
modalApi.close();
|
||||
},
|
||||
onConfirm: async () => {
|
||||
if (!storeId.value) {
|
||||
message.warning('请选择诊所');
|
||||
return;
|
||||
}
|
||||
if (!searchTime.value?.[0] || !searchTime.value?.[1]) {
|
||||
message.warning('请选择时间范围');
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await exportReconciliationExcelApi({
|
||||
store_id: storeId.value,
|
||||
search_time: searchTime.value,
|
||||
drug_id: drugSelect.value?.value,
|
||||
order_id: orderSelect.value?.value,
|
||||
by_order: byOrder.value ? 1 : 0,
|
||||
});
|
||||
const end = searchTime.value[1].format('YYYY-MM-DD');
|
||||
const safeName = (storeLabel.value || '诊所').replace(/[\\/:*?"<>|]+/g, '_');
|
||||
downloadByData(res.data, `${safeName}_${end}.xlsx`);
|
||||
message.success('导出成功');
|
||||
modalApi.close();
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || '导出失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
if (isOpen) {
|
||||
const data =
|
||||
modalApi.getData<{
|
||||
search_time?: [Dayjs, Dayjs];
|
||||
store_export_locked?: boolean;
|
||||
locked_store_id?: number;
|
||||
locked_store_name?: string;
|
||||
drug_select?: SelectLabeled;
|
||||
order_select?: SelectLabeled;
|
||||
by_order?: boolean;
|
||||
}>() || {};
|
||||
if (data.search_time?.[0] && data.search_time?.[1]) {
|
||||
searchTime.value = [dayjs(data.search_time[0]), dayjs(data.search_time[1])];
|
||||
} else {
|
||||
const r = loadPageSearchTimeRange();
|
||||
searchTime.value = [r[0], r[1]];
|
||||
}
|
||||
drugSelect.value = data.drug_select;
|
||||
orderSelect.value = data.order_select;
|
||||
byOrder.value = !!data.by_order;
|
||||
drugSelectOptions.value = [];
|
||||
orderSelectOptions.value = [];
|
||||
|
||||
storeExportLocked.value = !!data.store_export_locked;
|
||||
if (storeExportLocked.value && data.locked_store_id != null) {
|
||||
storeId.value = data.locked_store_id;
|
||||
storeLabel.value = data.locked_store_name || '本诊所';
|
||||
} else {
|
||||
storeId.value = undefined;
|
||||
storeLabel.value = '';
|
||||
loadStores();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[560px]" title="导出对账 Excel">
|
||||
<div class="flex flex-col gap-4 py-2">
|
||||
<div v-if="!storeExportLocked">
|
||||
<div class="mb-1 text-sm text-muted-foreground">诊所</div>
|
||||
<Select
|
||||
v-model:value="storeId"
|
||||
show-search
|
||||
:filter-option="
|
||||
(input: string, option: any) =>
|
||||
(option?.label ?? '').toLowerCase().includes(input.toLowerCase())
|
||||
"
|
||||
:options="storeOptions"
|
||||
:loading="fetchingStores"
|
||||
allow-clear
|
||||
placeholder="请选择诊所"
|
||||
class="w-full"
|
||||
option-filter-prop="label"
|
||||
/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="mb-1 text-sm text-muted-foreground">诊所</div>
|
||||
<div class="rounded border px-3 py-2 text-sm">
|
||||
{{ storeLabel || '本诊所' }}(ID: {{ storeId }})
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1 text-sm text-muted-foreground">时间范围</div>
|
||||
<DatePicker.RangePicker
|
||||
v-model:value="searchTime"
|
||||
class="w-full"
|
||||
format="YYYY-MM-DD"
|
||||
:get-popup-container="popupContainerBody"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox v-model:checked="byOrder">按订单维度导出(药品 sheet 与列表勾选一致)</Checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1 text-sm text-muted-foreground">药品(可选)</div>
|
||||
<Select
|
||||
v-model:value="drugSelect"
|
||||
label-in-value
|
||||
:filter-option="false"
|
||||
show-search
|
||||
allow-clear
|
||||
:disabled="!storeId"
|
||||
placeholder="先选诊所后搜索"
|
||||
:options="drugSelectOptions"
|
||||
class="w-full"
|
||||
@search="searchDrugOptions"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1 text-sm text-muted-foreground">订单(可选)</div>
|
||||
<Select
|
||||
v-model:value="orderSelect"
|
||||
label-in-value
|
||||
:filter-option="false"
|
||||
show-search
|
||||
allow-clear
|
||||
:disabled="!storeId"
|
||||
placeholder="先选诊所后搜索"
|
||||
:options="orderSelectOptions"
|
||||
class="w-full"
|
||||
@search="searchOrderOptions"
|
||||
/>
|
||||
</div>
|
||||
<Space v-if="!storeExportLocked">
|
||||
<Button @click="loadStores()">刷新诊所列表</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -1,84 +0,0 @@
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const reconciliationStoreSearch: VbenFormProps = {
|
||||
// 默认展开
|
||||
collapsed: false,
|
||||
schema: [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '请输入诊所名称',
|
||||
},
|
||||
defaultValue: '',
|
||||
fieldName: 'store_name',
|
||||
label: '诊所名称',
|
||||
},
|
||||
{
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
|
||||
},
|
||||
defaultValue: [dayjs().startOf('month'), dayjs()],
|
||||
fieldName: 'search_time',
|
||||
label: '时间范围',
|
||||
},
|
||||
],
|
||||
// 控制表单是否显示折叠按钮
|
||||
showCollapseButton: true,
|
||||
submitButtonOptions: {
|
||||
content: '查询',
|
||||
},
|
||||
// 是否在字段值改变时提交表单
|
||||
submitOnChange: true,
|
||||
// 按下回车时是否提交表单
|
||||
submitOnEnter: false,
|
||||
};
|
||||
|
||||
export const reconciliationDrugSearch: VbenFormProps = {
|
||||
// 默认展开
|
||||
collapsed: false,
|
||||
schema: [
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
placeholder: '请输入诊所ID',
|
||||
},
|
||||
defaultValue: '',
|
||||
fieldName: 'store_id',
|
||||
dependencies: {
|
||||
show: false,
|
||||
triggerFields: ['store_id'],
|
||||
},
|
||||
label: '诊所ID',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '请输入药品名称',
|
||||
},
|
||||
defaultValue: '',
|
||||
fieldName: 'drug_name',
|
||||
label: '药品名称',
|
||||
},
|
||||
{
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
|
||||
},
|
||||
defaultValue: [dayjs().startOf('month'), dayjs()],
|
||||
fieldName: 'search_time',
|
||||
label: '时间范围',
|
||||
},
|
||||
],
|
||||
// 控制表单是否显示折叠按钮
|
||||
showCollapseButton: true,
|
||||
submitButtonOptions: {
|
||||
content: '查询',
|
||||
},
|
||||
// 是否在字段值改变时提交表单
|
||||
submitOnChange: true,
|
||||
// 按下回车时是否提交表单
|
||||
submitOnEnter: false,
|
||||
};
|
||||
@@ -1,134 +0,0 @@
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
|
||||
interface RowType {
|
||||
id: string;
|
||||
nick_name: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
roles: { name: string }[];
|
||||
open_id: string;
|
||||
code: string;
|
||||
platform_id: string;
|
||||
phone: string;
|
||||
desc: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export const storeReconciliationTable: VxeGridProps<RowType> = {
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
labelField: '',
|
||||
},
|
||||
columnConfig: {
|
||||
useKey: true,
|
||||
},
|
||||
rowConfig: {
|
||||
useKey: true,
|
||||
},
|
||||
columns: [
|
||||
{ field: 'name', title: '诊所名称' },
|
||||
{
|
||||
field: 'reconciliation.herbal_sales',
|
||||
title: '草药',
|
||||
slots: { default: 'herbal' },
|
||||
},
|
||||
// { field: 'reconciliation.herbal_sales', title: '草药销售数量' },
|
||||
// { field: 'reconciliation.herbal_sales_price', title: '草药销售总价' },
|
||||
// { field: 'reconciliation.herbal_supply_price', title: '草药供货总价' },
|
||||
{
|
||||
field: 'reconciliation.medicine_sales',
|
||||
title: '西药',
|
||||
slots: { default: 'medicine' },
|
||||
},
|
||||
// { field: 'reconciliation.medicine_sales', title: '西药销售数量' },
|
||||
// { field: 'reconciliation.medicine_sales_price', title: '西药销售总价' },
|
||||
// { field: 'reconciliation.medicine_supply_price', title: '西药供货总价' },
|
||||
{
|
||||
field: 'reconciliation.service_package_sales',
|
||||
title: '服务包销售数量',
|
||||
slots: { default: 'service-package' },
|
||||
},
|
||||
// { field: 'reconciliation.service_package_sales', title: '服务包销售数量' },
|
||||
// {
|
||||
// field: 'reconciliation.service_package_sales_price',
|
||||
// title: '服务包销售总价',
|
||||
// },
|
||||
// {
|
||||
// field: 'reconciliation.service_package_supply_price',
|
||||
// title: '服务包供货总价',
|
||||
// },
|
||||
{ field: 'reconciliation.express_price', title: '其他费用', slots: { default: 'other'} },
|
||||
{ field: 'reconciliation.total_sales_price', title: '总销售总价' },
|
||||
{ field: 'reconciliation.total_supply_price', title: '总供货总价' },
|
||||
// { field: 'reconciliation.express_price', title: '快递费用' },
|
||||
// { field: 'reconciliation.process_price', title: '加工费用' },
|
||||
// { field: 'reconciliation.treatment_price', title: '治疗费用' },
|
||||
// { field: 'reconciliation.outpatient_price', title: '门诊费用' },
|
||||
{ field: 'reconciliation.registration_price', title: '挂号费用' },
|
||||
{ type: 'html', title: '操作', slots: { default: 'action' } },
|
||||
],
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
border: false,
|
||||
toolbarConfig: {
|
||||
// 是否显示搜索表单控制按钮
|
||||
// @ts-ignore 正式环境时有完整的类型声明
|
||||
search: true,
|
||||
refresh: true, // 刷新
|
||||
print: false, // 打印
|
||||
export: false, // 导出
|
||||
// custom: true, // 自定义列
|
||||
zoom: true, // 最大化最小化
|
||||
slots: {
|
||||
buttons: 'toolbar-buttons',
|
||||
},
|
||||
custom: {
|
||||
// 自定义列-图标
|
||||
icon: 'vxe-icon-menu',
|
||||
},
|
||||
},
|
||||
showOverflow: false,
|
||||
};
|
||||
|
||||
export const drugReconciliationTable: VxeGridProps<RowType> = {
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
labelField: '',
|
||||
},
|
||||
columnConfig: {
|
||||
useKey: true,
|
||||
},
|
||||
rowConfig: {
|
||||
useKey: true,
|
||||
},
|
||||
columns: [
|
||||
{ field: 'drug_name', title: '药名' },
|
||||
{ field: 'type_txt', title: '药品类型' },
|
||||
{ field: 'number', title: '出售数量' },
|
||||
{ field: 'total_sales_price', title: '总销售金额' },
|
||||
{ field: 'total_supply_price', title: '总供货金额' },
|
||||
// { type: 'html', title: '操作', slots: { default: 'action' } },
|
||||
],
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
border: false,
|
||||
toolbarConfig: {
|
||||
// 是否显示搜索表单控制按钮
|
||||
// @ts-ignore 正式环境时有完整的类型声明
|
||||
search: true,
|
||||
refresh: true, // 刷新
|
||||
print: false, // 打印
|
||||
export: false, // 导出
|
||||
// custom: true, // 自定义列
|
||||
zoom: true, // 最大化最小化
|
||||
slots: {
|
||||
buttons: 'toolbar-buttons',
|
||||
},
|
||||
custom: {
|
||||
// 自定义列-图标
|
||||
icon: 'vxe-icon-menu',
|
||||
},
|
||||
},
|
||||
showOverflow: false,
|
||||
};
|
||||
@@ -1,191 +1,352 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
import { AnalysisChartsTabs, Page } from '@vben/common-ui';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { Tag, Tabs, TabPane } from 'ant-design-vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
import { useUserStore } from '@vben/stores';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Input,
|
||||
RadioGroup,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
TabPane,
|
||||
Tabs,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
import {
|
||||
getReconciliationDrugList,
|
||||
getReconciliationStoreList
|
||||
getReconciliationDrugOptions,
|
||||
getReconciliationOrderOptions,
|
||||
getReconciliationStoreList,
|
||||
} from '#/views/finance/reconciliation/api';
|
||||
|
||||
import ReconciliationExportModal from './components/ReconciliationExportModal.vue';
|
||||
import StatisticsReconciliation from './components/statistics.vue';
|
||||
import { reconciliationDrugSearch, reconciliationStoreSearch } from './config/search';
|
||||
import { drugReconciliationTable, storeReconciliationTable } from './config/table';
|
||||
import {
|
||||
loadExcludeZeroSales,
|
||||
loadPageSearchTimeRange,
|
||||
saveExcludeZeroSales,
|
||||
savePageSearchTimeRange,
|
||||
} from './utils/formCache';
|
||||
|
||||
const [Grid, GridApi] = useVbenVxeGrid({
|
||||
formOptions: reconciliationStoreSearch,
|
||||
gridOptions: storeReconciliationTable,
|
||||
/** 诊所列表一次拉全量(后端 paginate) */
|
||||
const STORE_LIST_PAGE_SIZE = 99_999;
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const userInfo = computed(() => userStore.userInfo as Record<string, any> | null);
|
||||
|
||||
/** 平台用户(user_type === 2):药品未选诊所时为全平台汇总 */
|
||||
const isPlatformUser = computed(
|
||||
() => Number(userInfo.value?.roles?.user_type) === 2,
|
||||
);
|
||||
|
||||
/** 诊所侧账号(user_type === 1),药品仅本诊所,由后端强制 */
|
||||
const isStoreClinicUser = computed(
|
||||
() => Number(userInfo.value?.roles?.user_type) === 1 && !!userInfo.value?.store_id,
|
||||
);
|
||||
|
||||
/** 下拉选项用的诊所 ID:从诊所列表切入单店 / 诊所账号本店 */
|
||||
const optionsStoreId = computed((): number | undefined => {
|
||||
if (currentStoreId.value != null) {
|
||||
return currentStoreId.value;
|
||||
}
|
||||
const sid = userInfo.value?.store_id;
|
||||
if (isStoreClinicUser.value && sid != null && sid !== '') {
|
||||
return Number(sid);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const [DrugGrid, DrugGridApi] = useVbenVxeGrid({
|
||||
formOptions: reconciliationDrugSearch,
|
||||
gridOptions: drugReconciliationTable,
|
||||
/** 诊所管理员不展示「诊所范围」筛选 */
|
||||
const showExcludeZeroSalesFilter = computed(() => !isStoreClinicUser.value);
|
||||
|
||||
const [ExportModal, exportModalApi] = useVbenModal({
|
||||
connectedComponent: ReconciliationExportModal,
|
||||
});
|
||||
|
||||
const activeTab = ref('store');
|
||||
|
||||
const statistics = ref();
|
||||
const initTableAjax = () => {
|
||||
GridApi.setGridOptions({
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
// 请求后端接口方法
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getReconciliationStoreList({
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
}).then((res) => {
|
||||
statistics.value = null;
|
||||
statistics.value = [
|
||||
{
|
||||
title: '总销售金额',
|
||||
value: res.count.total_sales_price,
|
||||
icon: 'mdi:cash-multiple',
|
||||
color: 'text-blue-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '总供货额',
|
||||
value: res.count.total_supply_price,
|
||||
icon: 'mdi:truck-delivery',
|
||||
color: 'text-gray-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '草药销量',
|
||||
value: res.count.herbal_sales,
|
||||
icon: 'mdi:leaf',
|
||||
color: 'text-green-600',
|
||||
},
|
||||
{
|
||||
title: '草药销售金额',
|
||||
value: res.count.herbal_sales_price,
|
||||
icon: 'mdi:currency-cny',
|
||||
color: 'text-green-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '草药供货金额',
|
||||
value: res.count.herbal_supply_price,
|
||||
icon: 'mdi:package-variant',
|
||||
color: 'text-green-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '成药销量',
|
||||
value: res.count.medicine_sales,
|
||||
icon: 'mdi:pill',
|
||||
color: 'text-purple-600',
|
||||
},
|
||||
{
|
||||
title: '成药销售金额',
|
||||
value: res.count.medicine_sales_price,
|
||||
icon: 'mdi:currency-usd',
|
||||
color: 'text-purple-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '成药供货金额',
|
||||
value: res.count.medicine_supply_price,
|
||||
icon: 'mdi:pharmacy',
|
||||
color: 'text-purple-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '快递费',
|
||||
value: res.count.express_price,
|
||||
icon: 'skill-icons:expressjs-dark',
|
||||
color: 'text-orange-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '加工费',
|
||||
value: res.count.process_price,
|
||||
icon: 'mdi:factory',
|
||||
color: 'text-yellow-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '诊疗费',
|
||||
value: res.count.treatment_price,
|
||||
icon: 'mdi:medical-bag',
|
||||
color: 'text-red-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '门诊收入',
|
||||
value: res.count.outpatient_price,
|
||||
icon: 'mdi:hospital-box',
|
||||
color: 'text-blue-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '挂号费',
|
||||
value: res.count.registration_price,
|
||||
icon: 'mdi:card-account-details',
|
||||
color: 'text-pink-600',
|
||||
unit: '¥',
|
||||
},
|
||||
];
|
||||
return res.list;
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// 药品
|
||||
DrugGridApi.setGridOptions({
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
// 请求后端接口方法
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getReconciliationDrugList({
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
}).then((res) => {
|
||||
statistics.value = null;
|
||||
statistics.value = [
|
||||
{
|
||||
title: '总销售金额',
|
||||
value: res.count.total_sales_price,
|
||||
icon: 'mdi:cash-multiple',
|
||||
color: 'text-blue-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '总供货额',
|
||||
value: res.count.total_supply_price,
|
||||
icon: 'mdi:truck-delivery',
|
||||
color: 'text-gray-600',
|
||||
unit: '¥',
|
||||
},
|
||||
{
|
||||
title: '销量',
|
||||
value: res.count.number,
|
||||
icon: 'streamline-emojis:pill',
|
||||
color: 'text-purple-600',
|
||||
},
|
||||
];
|
||||
return res.list;
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
/** 整页共用时间 */
|
||||
const searchTime = ref<[Dayjs, Dayjs]>([dayjs().startOf('month'), dayjs()]);
|
||||
const storeName = ref('');
|
||||
const excludeZeroSales = ref(loadExcludeZeroSales());
|
||||
|
||||
const goDrug = (storeId) => {
|
||||
reconciliationDrugSearch.schema[0].value = storeId;
|
||||
type ReconciliationSelectOption = { value: number; label: string };
|
||||
|
||||
const drugSelect = ref<ReconciliationSelectOption | undefined>(undefined);
|
||||
const orderSelect = ref<ReconciliationSelectOption | undefined>(undefined);
|
||||
const drugSelectOptions = ref<ReconciliationSelectOption[]>([]);
|
||||
const orderSelectOptions = ref<ReconciliationSelectOption[]>([]);
|
||||
const byOrder = ref(false);
|
||||
|
||||
/** 平台/经理等从诊所表「查看」切入单店药品时携带;null 表示不按单店过滤(平台为全量汇总) */
|
||||
const currentStoreId = ref<number | null>(null);
|
||||
const currentStoreName = ref('');
|
||||
|
||||
const statistics = ref<any[] | null>(null);
|
||||
|
||||
const storeLoading = ref(false);
|
||||
const storeList = ref<any[]>([]);
|
||||
|
||||
const drugLoading = ref(false);
|
||||
const drugList = ref<any[]>([]);
|
||||
|
||||
function searchTimeParam(): [string, string] {
|
||||
return [searchTime.value[0].format('YYYY-MM-DD'), searchTime.value[1].format('YYYY-MM-DD')];
|
||||
}
|
||||
|
||||
function setStoreStatistics(count: Record<string, any>) {
|
||||
statistics.value = [
|
||||
{ title: '总销售金额', value: count.total_sales_price, icon: 'mdi:cash-multiple', color: 'text-blue-600', unit: '¥' },
|
||||
{ title: '总供货额', value: count.total_supply_price, icon: 'mdi:truck-delivery', color: 'text-muted-foreground', unit: '¥' },
|
||||
{ title: '草药销量', value: count.herbal_sales, icon: 'mdi:leaf', color: 'text-green-600' },
|
||||
{ title: '草药销售金额', value: count.herbal_sales_price, icon: 'mdi:currency-cny', color: 'text-green-600', unit: '¥' },
|
||||
{ title: '草药供货金额', value: count.herbal_supply_price, icon: 'mdi:package-variant', color: 'text-green-600', unit: '¥' },
|
||||
{ title: '成药销量', value: count.medicine_sales, icon: 'mdi:pill', color: 'text-purple-600' },
|
||||
{ title: '成药销售金额', value: count.medicine_sales_price, icon: 'mdi:currency-usd', color: 'text-purple-600', unit: '¥' },
|
||||
{ title: '成药供货金额', value: count.medicine_supply_price, icon: 'mdi:pharmacy', color: 'text-purple-600', unit: '¥' },
|
||||
{ title: '快递费', value: count.express_price, icon: 'skill-icons:expressjs-dark', color: 'text-orange-600', unit: '¥' },
|
||||
{ title: '加工费', value: count.process_price, icon: 'mdi:factory', color: 'text-yellow-600', unit: '¥' },
|
||||
{ title: '诊疗费', value: count.treatment_price, icon: 'mdi:medical-bag', color: 'text-red-600', unit: '¥' },
|
||||
{ title: '门诊收入', value: count.outpatient_price, icon: 'mdi:hospital-box', color: 'text-blue-600', unit: '¥' },
|
||||
{ title: '挂号费', value: count.registration_price, icon: 'mdi:card-account-details', color: 'text-pink-600', unit: '¥' },
|
||||
];
|
||||
}
|
||||
|
||||
function setDrugStatistics(count: Record<string, any>) {
|
||||
statistics.value = [
|
||||
{ title: '总销售金额', value: count.total_sales_price, icon: 'mdi:cash-multiple', color: 'text-blue-600', unit: '¥' },
|
||||
{ title: '总供货额', value: count.total_supply_price, icon: 'mdi:truck-delivery', color: 'text-muted-foreground', unit: '¥' },
|
||||
{ title: '销量', value: count.number, icon: 'streamline-emojis:pill', color: 'text-purple-600' },
|
||||
];
|
||||
}
|
||||
|
||||
function buildDrugListParams(): Record<string, unknown> {
|
||||
const params: Record<string, unknown> = {
|
||||
search_time: searchTimeParam(),
|
||||
};
|
||||
if (drugSelect.value?.value) {
|
||||
params.drug_id = drugSelect.value.value;
|
||||
}
|
||||
if (orderSelect.value?.value) {
|
||||
params.order_id = orderSelect.value.value;
|
||||
}
|
||||
if (byOrder.value) {
|
||||
params.by_order = 1;
|
||||
}
|
||||
if (currentStoreId.value != null) {
|
||||
params.store_id = currentStoreId.value;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
const searchDrugOptions = debounce(async (raw: string) => {
|
||||
const st = searchTimeParam();
|
||||
try {
|
||||
const res = await getReconciliationDrugOptions({
|
||||
q: raw,
|
||||
search_time: st,
|
||||
...(optionsStoreId.value != null ? { store_id: optionsStoreId.value } : {}),
|
||||
});
|
||||
drugSelectOptions.value = (res.items || []).map((i) => ({ label: i.label, value: i.value }));
|
||||
} catch {
|
||||
drugSelectOptions.value = [];
|
||||
}
|
||||
}, 300);
|
||||
|
||||
const searchOrderOptions = debounce(async (raw: string) => {
|
||||
const sid = optionsStoreId.value;
|
||||
if (sid == null) {
|
||||
orderSelectOptions.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await getReconciliationOrderOptions({
|
||||
q: raw,
|
||||
store_id: sid,
|
||||
search_time: searchTimeParam(),
|
||||
});
|
||||
orderSelectOptions.value = (res.items || []).map((i) => ({ label: i.label, value: i.value }));
|
||||
} catch {
|
||||
orderSelectOptions.value = [];
|
||||
}
|
||||
}, 300);
|
||||
|
||||
async function fetchStoreList() {
|
||||
storeLoading.value = true;
|
||||
try {
|
||||
const res = await getReconciliationStoreList({
|
||||
page: 1,
|
||||
pageSize: STORE_LIST_PAGE_SIZE,
|
||||
store_name: storeName.value,
|
||||
search_time: searchTimeParam(),
|
||||
exclude_zero_sales: isStoreClinicUser.value ? 0 : excludeZeroSales.value,
|
||||
});
|
||||
setStoreStatistics(res.count);
|
||||
const list = res.list;
|
||||
storeList.value = list?.items ?? [];
|
||||
} finally {
|
||||
storeLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDrugList() {
|
||||
drugLoading.value = true;
|
||||
try {
|
||||
const res = await getReconciliationDrugList(buildDrugListParams());
|
||||
setDrugStatistics(res.count);
|
||||
const list = res.list;
|
||||
drugList.value = list?.items ?? [];
|
||||
} finally {
|
||||
drugLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onSearchTimeChange() {
|
||||
if (!searchTime.value?.[0] || !searchTime.value?.[1]) {
|
||||
return;
|
||||
}
|
||||
savePageSearchTimeRange(searchTime.value);
|
||||
if (activeTab.value === 'store') {
|
||||
fetchStoreList();
|
||||
} else if (activeTab.value === 'drug') {
|
||||
fetchDrugList();
|
||||
}
|
||||
}
|
||||
|
||||
function onExcludeZeroChange() {
|
||||
saveExcludeZeroSales(excludeZeroSales.value);
|
||||
if (activeTab.value === 'store') {
|
||||
fetchStoreList();
|
||||
}
|
||||
}
|
||||
|
||||
function queryStore() {
|
||||
fetchStoreList();
|
||||
}
|
||||
|
||||
function queryDrug() {
|
||||
fetchDrugList();
|
||||
}
|
||||
|
||||
function openExportModal() {
|
||||
const u = userInfo.value;
|
||||
const locked =
|
||||
Number(u?.roles?.user_type) === 1 && u?.store_id != null && u.store_id !== '';
|
||||
exportModalApi.setData({
|
||||
search_time: searchTime.value,
|
||||
store_export_locked: locked,
|
||||
locked_store_id: locked ? Number(u.store_id) : undefined,
|
||||
locked_store_name:
|
||||
(u?.store_name as string) ||
|
||||
(u?.store?.name as string) ||
|
||||
(u?.nick_name as string) ||
|
||||
'',
|
||||
drug_select: drugSelect.value,
|
||||
order_select: orderSelect.value,
|
||||
by_order: byOrder.value,
|
||||
});
|
||||
exportModalApi.open();
|
||||
}
|
||||
|
||||
function goDrug(storeId: number, name: string) {
|
||||
currentStoreId.value = storeId;
|
||||
currentStoreName.value = name;
|
||||
activeTab.value = 'drug';
|
||||
}
|
||||
|
||||
initTableAjax();
|
||||
function clearDrugStoreFilter() {
|
||||
currentStoreId.value = null;
|
||||
currentStoreName.value = '';
|
||||
if (activeTab.value === 'drug') {
|
||||
fetchDrugList();
|
||||
}
|
||||
}
|
||||
|
||||
const drugAlertMessage = computed(() => {
|
||||
if (isStoreClinicUser.value) {
|
||||
const sid = userInfo.value?.store_id;
|
||||
const name =
|
||||
(userInfo.value?.store_name as string) ||
|
||||
(userInfo.value?.store?.name as string) ||
|
||||
'';
|
||||
const parts = ['当前为诊所账号,仅展示本诊所药品数据。'];
|
||||
if (name) parts.push(`诊所:${name}`);
|
||||
if (sid != null && sid !== '') parts.push(`(ID: ${sid})`);
|
||||
return parts.join('');
|
||||
}
|
||||
if (currentStoreId.value != null) {
|
||||
return `当前查看诊所:${currentStoreName.value || '—'}(ID: ${currentStoreId.value})`;
|
||||
}
|
||||
if (isPlatformUser.value) {
|
||||
return '当前为全部诊所药品汇总。可在「诊所」列表点击「查看」切换为单个诊所;切换后也可点击「查看全部诊所汇总」恢复。';
|
||||
}
|
||||
return '未指定单个诊所时,将按账号权限展示对应范围内的药品汇总(与后端规则一致)。可在「诊所」列表点击「查看」筛选单个诊所。';
|
||||
});
|
||||
|
||||
const storeColumns = [
|
||||
{ title: '诊所名称', dataIndex: 'name', key: 'name', width: 160, ellipsis: true },
|
||||
{ title: '草药', key: 'herbal', width: 240 },
|
||||
{ title: '西药', key: 'medicine', width: 240 },
|
||||
{ title: '服务包', key: 'servicePackage', width: 240 },
|
||||
{ title: '其他费用', key: 'other', width: 200 },
|
||||
{ title: '总销售总价', key: 'total_sales_price', width: 120 },
|
||||
{ title: '总供货总价', key: 'total_supply_price', width: 120 },
|
||||
{ title: '挂号费用', key: 'registration_price', width: 100 },
|
||||
{ title: '操作', key: 'action', width: 100, fixed: 'right' as const },
|
||||
];
|
||||
|
||||
const drugColumnsAll = [
|
||||
{ title: '药名', dataIndex: 'drug_name', key: 'drug_name', width: 200, ellipsis: true },
|
||||
{ title: '药品编号', dataIndex: 'drug_number', key: 'drug_number', width: 120, ellipsis: true },
|
||||
{ title: '订单编号', dataIndex: 'order_no', key: 'order_no', width: 160, ellipsis: true },
|
||||
{ title: '药品类型', dataIndex: 'type_txt', key: 'type_txt', width: 120 },
|
||||
{ title: '出售数量', dataIndex: 'number', key: 'number', width: 100 },
|
||||
{ title: '供货价', dataIndex: 'market_price', key: 'market_price', width: 100 },
|
||||
{ title: '总销售金额', dataIndex: 'total_sales_price', key: 'total_sales_price', width: 120 },
|
||||
{ title: '总供货金额', dataIndex: 'total_supply_price', key: 'total_supply_price', width: 120 },
|
||||
];
|
||||
|
||||
const drugColumns = computed(() =>
|
||||
byOrder.value ? drugColumnsAll : drugColumnsAll.filter((c) => c.key !== 'order_no'),
|
||||
);
|
||||
|
||||
function popupContainerBody() {
|
||||
return document.body;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
searchTime.value = loadPageSearchTimeRange();
|
||||
excludeZeroSales.value = loadExcludeZeroSales();
|
||||
fetchStoreList();
|
||||
});
|
||||
|
||||
watch(activeTab, (key) => {
|
||||
if (key === 'store') {
|
||||
fetchStoreList();
|
||||
} else if (key === 'drug') {
|
||||
fetchDrugList();
|
||||
}
|
||||
});
|
||||
|
||||
watch(byOrder, () => {
|
||||
if (activeTab.value === 'drug') {
|
||||
fetchDrugList();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -193,243 +354,186 @@ initTableAjax();
|
||||
auto-content-height
|
||||
title="对账单"
|
||||
>
|
||||
<ExportModal />
|
||||
<StatisticsReconciliation
|
||||
v-if="statistics"
|
||||
:statistics="statistics"
|
||||
/>
|
||||
|
||||
<Tabs v-model:active-key="activeTab" type="card" :destroyInactiveTabPane="true">
|
||||
<div class="mb-3 flex flex-wrap items-center gap-3 rounded border px-3 py-2">
|
||||
<span class="text-sm text-muted-foreground">时间范围(全页共用)</span>
|
||||
<DatePicker.RangePicker
|
||||
v-model:value="searchTime"
|
||||
format="YYYY-MM-DD"
|
||||
:get-popup-container="popupContainerBody"
|
||||
@change="onSearchTimeChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Tabs v-model:active-key="activeTab" type="card" :destroy-inactive-tab-pane="false">
|
||||
<TabPane key="store" tab="诊所">
|
||||
<Grid>
|
||||
<template #toolbar-buttons></template>
|
||||
<template #toolbar-tools></template>
|
||||
<template #herbal="{ row }">
|
||||
<div class="mt-2">
|
||||
<Tag color="green">
|
||||
销售数量:{{ row.reconciliation.herbal_sales }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Tag color="orange">
|
||||
销售总价:{{ row.reconciliation.herbal_sales_price }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Tag color="red">
|
||||
供货总价:{{ row.reconciliation.herbal_supply_price }}
|
||||
</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #medicine="{ row }">
|
||||
<div class="mt-2">
|
||||
<Tag color="green">
|
||||
销售数量:{{ row.reconciliation.medicine_sales }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Tag color="orange">
|
||||
销售总价:{{ row.reconciliation.medicine_sales_price }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Tag color="red">
|
||||
供货总价:{{ row.reconciliation.medicine_supply_price }}
|
||||
</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #service-package="{ row }">
|
||||
<div class="mt-2">
|
||||
<Tag color="green">
|
||||
销售数量:{{ row.reconciliation.service_package_sales }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Tag color="orange">
|
||||
销售总价:{{ row.reconciliation.service_package_sales_price }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Tag color="red">
|
||||
供货总价:{{ row.reconciliation.service_package_supply_price }}
|
||||
</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #other="{ row }">
|
||||
<div class="mt-2">
|
||||
<Tag color="green">
|
||||
快递费用:{{ row.reconciliation.express_price }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Tag color="green">
|
||||
加工费:{{ row.reconciliation.process_price }}
|
||||
</Tag>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<Tag color="green">
|
||||
诊疗费:{{ row.reconciliation.treatment_price }}
|
||||
</Tag>
|
||||
</div>
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 诊疗费:{{ row.reconciliation.outpatient_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '查看',
|
||||
type: 'link',
|
||||
icon: 'marketeq:eye',
|
||||
size: 'small',
|
||||
onClick: goDrug.bind(null, row.id),
|
||||
},
|
||||
]"
|
||||
:drop-down-actions="[]"
|
||||
<div class="mb-3 flex flex-wrap items-end gap-3">
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-muted-foreground">诊所名称</div>
|
||||
<Input
|
||||
v-model:value="storeName"
|
||||
allow-clear
|
||||
placeholder="诊所名称或拼音首拼"
|
||||
class="w-52"
|
||||
@press-enter="queryStore"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showExcludeZeroSalesFilter">
|
||||
<div class="mb-1 text-xs text-muted-foreground">诊所范围</div>
|
||||
<RadioGroup
|
||||
v-model:value="excludeZeroSales"
|
||||
button-style="solid"
|
||||
option-type="button"
|
||||
:options="[
|
||||
{ label: '隐藏销售额为0的诊所', value: 1 },
|
||||
{ label: '显示全部诊所', value: 0 },
|
||||
]"
|
||||
@change="onExcludeZeroChange"
|
||||
/>
|
||||
</div>
|
||||
<Space>
|
||||
<Button type="primary" @click="queryStore">查询</Button>
|
||||
<Button type="primary" @click="openExportModal">导出 Excel</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
:columns="storeColumns"
|
||||
:data-source="storeList"
|
||||
:loading="storeLoading"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
:scroll="{ x: 1400 }"
|
||||
bordered
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'herbal'">
|
||||
<div class="flex flex-col gap-1">
|
||||
<Tag color="green">销售数量:{{ record.reconciliation?.herbal_sales }}</Tag>
|
||||
<Tag color="orange">销售总价:{{ record.reconciliation?.herbal_sales_price }}</Tag>
|
||||
<Tag color="red">供货总价:{{ record.reconciliation?.herbal_supply_price }}</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'medicine'">
|
||||
<div class="flex flex-col gap-1">
|
||||
<Tag color="green">销售数量:{{ record.reconciliation?.medicine_sales }}</Tag>
|
||||
<Tag color="orange">销售总价:{{ record.reconciliation?.medicine_sales_price }}</Tag>
|
||||
<Tag color="red">供货总价:{{ record.reconciliation?.medicine_supply_price }}</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'servicePackage'">
|
||||
<div class="flex flex-col gap-1">
|
||||
<Tag color="green">销售数量:{{ record.reconciliation?.service_package_sales }}</Tag>
|
||||
<Tag color="orange">销售总价:{{ record.reconciliation?.service_package_sales_price }}</Tag>
|
||||
<Tag color="red">供货总价:{{ record.reconciliation?.service_package_supply_price }}</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'other'">
|
||||
<div class="flex flex-col gap-1">
|
||||
<Tag color="green">快递费用:{{ record.reconciliation?.express_price }}</Tag>
|
||||
<Tag color="green">加工费:{{ record.reconciliation?.process_price }}</Tag>
|
||||
<Tag color="green">诊疗费:{{ record.reconciliation?.treatment_price }}</Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'total_sales_price'">
|
||||
{{ record.reconciliation?.total_sales_price }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'total_supply_price'">
|
||||
{{ record.reconciliation?.total_supply_price }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'registration_price'">
|
||||
{{ record.reconciliation?.registration_price }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '查看',
|
||||
type: 'link',
|
||||
icon: 'marketeq:eye',
|
||||
size: 'small',
|
||||
onClick: () => goDrug(record.id, record.name),
|
||||
},
|
||||
]"
|
||||
:drop-down-actions="[]"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</Grid>
|
||||
</Table>
|
||||
</TabPane>
|
||||
|
||||
<TabPane key="drug" tab="药品">
|
||||
<DrugGrid>
|
||||
<template #toolbar-buttons></template>
|
||||
<template #toolbar-tools></template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
// {
|
||||
// label: '编辑',
|
||||
// type: 'link',
|
||||
// icon: 'uil:edit',
|
||||
// size: 'small',
|
||||
// // auth: ['admin', 'sys:role:detail'],
|
||||
// onClick: showModal.bind(null, row, true),
|
||||
// },
|
||||
]"
|
||||
:drop-down-actions="[]"
|
||||
<Alert
|
||||
type="info"
|
||||
show-icon
|
||||
class="mb-3"
|
||||
:message="drugAlertMessage"
|
||||
/>
|
||||
<div
|
||||
v-if="isPlatformUser && currentStoreId != null"
|
||||
class="mb-3"
|
||||
>
|
||||
<Button type="link" size="small" @click="clearDrugStoreFilter">
|
||||
查看全部诊所汇总
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 flex flex-wrap items-end gap-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<Checkbox v-model:checked="byOrder">按订单维度</Checkbox>
|
||||
<span class="text-xs text-muted-foreground">未勾选时按药品+供货价合并;勾选后按订单拆分</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-muted-foreground">药品</div>
|
||||
<Select
|
||||
v-model:value="drugSelect"
|
||||
label-in-value
|
||||
:filter-option="false"
|
||||
show-search
|
||||
allow-clear
|
||||
placeholder="输入名称或拼音搜索"
|
||||
:options="drugSelectOptions"
|
||||
class="min-w-56"
|
||||
@search="searchDrugOptions"
|
||||
/>
|
||||
</template>
|
||||
</DrugGrid>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1 text-xs text-muted-foreground">订单</div>
|
||||
<Select
|
||||
v-model:value="orderSelect"
|
||||
label-in-value
|
||||
:filter-option="false"
|
||||
show-search
|
||||
allow-clear
|
||||
:disabled="optionsStoreId == null"
|
||||
:placeholder="
|
||||
optionsStoreId == null ? '请先在本页选择单个诊所(诊所列表点「查看」)' : '输入订单号搜索'
|
||||
"
|
||||
:options="orderSelectOptions"
|
||||
class="min-w-56"
|
||||
@search="searchOrderOptions"
|
||||
/>
|
||||
</div>
|
||||
<Button type="primary" @click="queryDrug">查询</Button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
:columns="drugColumns"
|
||||
:data-source="drugList"
|
||||
:loading="drugLoading"
|
||||
:pagination="false"
|
||||
row-key="agg_key"
|
||||
:scroll="{ x: 1280 }"
|
||||
bordered
|
||||
size="small"
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
|
||||
<!-- <AnalysisChartsTabs :tabs="chartTabs" :value="activeTab" class="mt-5">-->
|
||||
<!-- <template #store>-->
|
||||
<!-- <Grid>-->
|
||||
<!-- <template #toolbar-buttons></template>-->
|
||||
<!-- <template #toolbar-tools></template>-->
|
||||
<!-- <template #herbal="{ row }">-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 销售数量:{{ row.reconciliation.herbal_sales }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 销售总价:{{ row.reconciliation.herbal_sales_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="warning">-->
|
||||
<!-- 供货总价:{{ row.reconciliation.herbal_supply_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- <template #medicine="{ row }">-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 销售数量:{{ row.reconciliation.medicine_sales }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 销售总价:{{ row.reconciliation.medicine_sales_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="warning">-->
|
||||
<!-- 供货总价:{{ row.reconciliation.medicine_supply_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- <template #service-package="{ row }">-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 销售数量:{{ row.reconciliation.service_package_sales }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 销售总价:{{ row.reconciliation.service_package_sales_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="warning">-->
|
||||
<!-- 供货总价:{{ row.reconciliation.service_package_supply_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- <template #other="{ row }">-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 快递费用:{{ row.reconciliation.express_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 加工费:{{ row.reconciliation.process_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 治疗费:{{ row.reconciliation.treatment_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="mt-2">-->
|
||||
<!-- <Tag color="green">-->
|
||||
<!-- 诊疗费:{{ row.reconciliation.outpatient_price }}-->
|
||||
<!-- </Tag>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- <template #action="{ row }">-->
|
||||
<!-- <TableAction-->
|
||||
<!-- :actions="[-->
|
||||
<!-- {-->
|
||||
<!-- label: '查看',-->
|
||||
<!-- type: 'link',-->
|
||||
<!-- icon: 'uil:edit',-->
|
||||
<!-- size: 'small',-->
|
||||
<!-- onClick: goDrug.bind(null, row, true),-->
|
||||
<!-- },-->
|
||||
<!-- ]"-->
|
||||
<!-- :drop-down-actions="[]"-->
|
||||
<!-- />-->
|
||||
<!-- </template>-->
|
||||
<!-- </Grid>-->
|
||||
<!-- </template>-->
|
||||
<!-- <template #drug>-->
|
||||
<!-- <DrugGrid>-->
|
||||
<!-- <template #toolbar-buttons></template>-->
|
||||
<!-- <template #toolbar-tools></template>-->
|
||||
<!-- <template #action="{ row }">-->
|
||||
<!-- <TableAction-->
|
||||
<!-- :actions="[-->
|
||||
<!-- // {-->
|
||||
<!-- // label: '编辑',-->
|
||||
<!-- // type: 'link',-->
|
||||
<!-- // icon: 'uil:edit',-->
|
||||
<!-- // size: 'small',-->
|
||||
<!-- // // auth: ['admin', 'sys:role:detail'],-->
|
||||
<!-- // onClick: showModal.bind(null, row, true),-->
|
||||
<!-- // },-->
|
||||
<!-- ]"-->
|
||||
<!-- :drop-down-actions="[]"-->
|
||||
<!-- />-->
|
||||
<!-- </template>-->
|
||||
<!-- </DrugGrid>-->
|
||||
<!-- </template>-->
|
||||
<!-- </AnalysisChartsTabs>-->
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
/** 整页共用时间(新) */
|
||||
const KEY_PAGE_SEARCH_TIME = 'reconciliation.page.search_time';
|
||||
/** 历史 key:迁移后删除 */
|
||||
const KEY_LEGACY_STORE_SEARCH_TIME = 'reconciliation.store.search_time';
|
||||
const KEY_LEGACY_DRUG_SEARCH_TIME = 'reconciliation.drug.search_time';
|
||||
|
||||
const KEY_EXCLUDE_ZERO_SALES = 'reconciliation.store.exclude_zero_sales';
|
||||
|
||||
export function defaultSearchTimeRange(): [Dayjs, Dayjs] {
|
||||
return [dayjs().startOf('month'), dayjs()];
|
||||
}
|
||||
|
||||
function parseStoredRange(raw: string | null): [Dayjs, Dayjs] | null {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const arr = JSON.parse(raw) as unknown;
|
||||
if (!Array.isArray(arr) || arr.length !== 2) return null;
|
||||
const a = dayjs(arr[0] as string);
|
||||
const b = dayjs(arr[1] as string);
|
||||
if (!a.isValid() || !b.isValid()) return null;
|
||||
return [a, b];
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function persistPageRange(range: [Dayjs, Dayjs]) {
|
||||
const a = range[0].format('YYYY-MM-DD');
|
||||
const b = range[1].format('YYYY-MM-DD');
|
||||
localStorage.setItem(KEY_PAGE_SEARCH_TIME, JSON.stringify([a, b]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取整页时间缓存;若无新 key 则从旧 store/drug key 迁移一次后删除旧项。
|
||||
*/
|
||||
export function loadPageSearchTimeRange(): [Dayjs, Dayjs] {
|
||||
const page = parseStoredRange(localStorage.getItem(KEY_PAGE_SEARCH_TIME));
|
||||
if (page) {
|
||||
return page;
|
||||
}
|
||||
const legacyStore = parseStoredRange(localStorage.getItem(KEY_LEGACY_STORE_SEARCH_TIME));
|
||||
const legacyDrug = parseStoredRange(localStorage.getItem(KEY_LEGACY_DRUG_SEARCH_TIME));
|
||||
const merged = legacyStore ?? legacyDrug ?? defaultSearchTimeRange();
|
||||
persistPageRange(merged);
|
||||
localStorage.removeItem(KEY_LEGACY_STORE_SEARCH_TIME);
|
||||
localStorage.removeItem(KEY_LEGACY_DRUG_SEARCH_TIME);
|
||||
return merged;
|
||||
}
|
||||
|
||||
function pickYmd(v: unknown): string {
|
||||
if (v == null) return '';
|
||||
if (typeof v === 'object' && v !== null && 'format' in v && typeof (v as { format: (s: string) => string }).format === 'function') {
|
||||
return (v as Dayjs).format('YYYY-MM-DD');
|
||||
}
|
||||
const d = dayjs(v as string | number);
|
||||
return d.isValid() ? d.format('YYYY-MM-DD') : '';
|
||||
}
|
||||
|
||||
export function savePageSearchTimeRange(range: unknown) {
|
||||
if (!range || !Array.isArray(range) || range.length < 2) return;
|
||||
const a = pickYmd(range[0]);
|
||||
const b = pickYmd(range[1]);
|
||||
if (!a || !b) return;
|
||||
localStorage.setItem(KEY_PAGE_SEARCH_TIME, JSON.stringify([a, b]));
|
||||
}
|
||||
|
||||
/** 1 = 隐藏销售额为 0 的诊所(默认),0 = 显示全部 */
|
||||
export function loadExcludeZeroSales(): number {
|
||||
const v = localStorage.getItem(KEY_EXCLUDE_ZERO_SALES);
|
||||
if (v === null) return 1;
|
||||
return v === '0' ? 0 : 1;
|
||||
}
|
||||
|
||||
export function saveExcludeZeroSales(v: number) {
|
||||
localStorage.setItem(KEY_EXCLUDE_ZERO_SALES, v === 0 ? '0' : '1');
|
||||
}
|
||||
Reference in New Issue
Block a user