feat:优化了订单、诊所搜索组件、药品这些交互

This commit is contained in:
李琦
2026-07-23 09:24:06 +08:00
parent ee4490a91c
commit 700db76f16
73 changed files with 1321 additions and 433 deletions

View File

@@ -57,7 +57,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}快递公司`" class="w-[30%]">
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}快递公司`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -37,7 +37,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%]" title="处方溯源">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="处方溯源">
<PrescriptionSourceContent :data="data" />
</Modal>
</template>

View File

@@ -21,7 +21,7 @@ export const gridOptions: VxeGridProps<RowType> = {
{ field: 'prescription_no', align: 'left', title: '处方订单号' },
{ field: 'doctor_info.name', align: 'left', title: '开方医生' },
{ field: 'user_patient.name', align: 'left', title: '就诊人名称' },
{ field: 'store.name', align: 'left', title: '开方诊所' },
{ field: 'store.name', align: 'left', title: '开方诊所', slots: { default: 'store-name' } },
{ field: 'status', title: '状态', slots: { default: 'status' } },
{
field: 'is_online',

View File

@@ -9,6 +9,7 @@ import { Button, Tabs, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { TableAction } from '#/components/table-action';
import StoreCardModal from '#/components/store-card/StoreCardModal.vue';
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
import { getPrescriptionListApi } from './api';
@@ -31,6 +32,16 @@ const gridEvents: VxeGridListeners<any> = {
},
};
const [StoreCardModalComp, StoreCardModalApi] = useVbenModal({
connectedComponent: StoreCardModal,
});
function openStoreCard(storeId?: number) {
if (!storeId) return;
StoreCardModalApi.setData({ storeId: Number(storeId) });
StoreCardModalApi.open();
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions: {
@@ -82,6 +93,7 @@ const openPrescriptionSourceModal = (id: number) => {
<Page auto-content-height title="处方管理">
<PrescriptionDetailModal />
<PrescriptionSourceModal />
<StoreCardModalComp />
<div class="mb-3">
<Tabs :active-key="statusTab" @change="onStatusTabChange">
<Tabs.TabPane key="0" tab="待审核" />
@@ -99,6 +111,18 @@ const openPrescriptionSourceModal = (id: number) => {
</template>
</TableAction>
</template>
<template #store-name="{ row }">
<Button
v-if="row.store?.id || row.store_id"
type="link"
size="small"
class="!px-0"
@click="openStoreCard(row.store?.id || row.store_id)"
>
{{ row.store?.name || '—' }}
</Button>
<span v-else>{{ row.store?.name || '—' }}</span>
</template>
<template #status="{ row }">
<div>
<Tag v-if="row.prescription_type === 1" color="orange">中药</Tag>

View File

@@ -249,7 +249,7 @@ function prescriptionStatusColor() {
}
</script>
<template>
<Modal class="w-[80%]" title="订单详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="订单详情">
<div v-if="data" class="flex flex-col gap-4">
<Space>
<Button type="primary" size="small" @click="openOrderTrace">

View File

@@ -65,7 +65,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`订单:【 ${orderNo} 】发货`" class="w-[30%]">
<Modal :title="`订单:【 ${orderNo} 】发货`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
title="退款"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -1,8 +1,7 @@
import type { VbenFormProps } from '#/adapter/form';
import dayjs from 'dayjs';
import {getOrderStatusOption} from "#/views/business/order/product-order/api";
import {getStoreOption} from "#/views/system/store/api";
import { getOrderStatusOption } from '#/views/business/order/product-order/api';
export const formOptions: VbenFormProps = {
// 默认展开
@@ -18,22 +17,13 @@ export const formOptions: VbenFormProps = {
label: '订单号',
},
{
component: 'ApiSelect',
// 门店多选:名称/拼音首拼气泡选择,诊所+药店
component: 'StoreMultiSearch',
componentProps: {
allowClear: true,
filterOption: true,
// showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: `${item.name}【${item.id}】`,
value: item.id,
}));
},
api: getStoreOption,
placeholder: '请选择',
placeholder: '输入名称或拼音首拼',
},
fieldName: 'store_id',
defaultValue: [],
fieldName: 'store_ids',
label: '门店',
},
{
@@ -41,8 +31,6 @@ export const formOptions: VbenFormProps = {
componentProps: {
allowClear: true,
filterOption: true,
// showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
@@ -117,21 +105,16 @@ export const formOptions: VbenFormProps = {
{
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
format: 'YYYY-MM-DD',
},
defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'search_time',
label: '时间范围',
},
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,
submitButtonOptions: {
content: '查询',
},
// 是否在字段值改变时提交表单
// submitOnChange: true,
// submitOnChange: true,
// 按下回车时是否提交表单
submitOnEnter: false,
};

View File

@@ -52,6 +52,7 @@ import type { QuickDiscountOption } from '#/utils/pricePercentAdjust';
import { normalizeQuickOptions } from '#/utils/pricePercentAdjust';
import PrescriptionDetail from '#/views/doctor/doctor-reception/components/PrescriptionDetail.vue';
import DoctorCardModal from '#/views/doctor/doctor/components/DoctorCardModal.vue';
import StoreCardModal from '#/components/store-card/StoreCardModal.vue';
import {
WxUserPatientDetailModal,
WxUserPatientsModal,
@@ -138,8 +139,8 @@ function buildFormOptionsFromRoute(): VbenFormProps {
const rawTimeScope = route.query.time_scope;
const timeScope = Array.isArray(rawTimeScope) ? rawTimeScope[0] : rawTimeScope;
const schema = (formOptions.schema ?? []).map((item) => {
if (item.fieldName === 'store_id') {
return { ...item, defaultValue: Number(storeId) };
if (item.fieldName === 'store_ids') {
return { ...item, defaultValue: [Number(storeId)] };
}
if (item.fieldName === 'search_time') {
if (timeScope === 'month') {
@@ -256,12 +257,15 @@ const collapseAll = () => {
const overviewItems = ref<AnalysisOverviewItem[]>([]);
const income = ref(0);
const total = ref(0);
/** 按当前列表筛选条件刷新顶部销售金额/收益(门店参数后端字段名为 id) */
/** 按当前列表筛选条件刷新顶部销售金额/收益(支持门店多选 store_ids) */
const saleAmount = (formValues?: Record<string, any>) => {
const values = formValues ?? gridApi.formApi.latestSubmissionValues ?? {};
saleAmountApi({
search_time: values.search_time,
id: values.store_id,
store_ids: values.store_ids,
id: Array.isArray(values.store_ids) && values.store_ids.length === 1
? values.store_ids[0]
: undefined,
}).then((res) => {
income.value = res.income;
total.value = res.total;
@@ -292,6 +296,16 @@ const [DoctorCardModals, DoctorCardModalApi] = useVbenModal({
connectedComponent: DoctorCardModal,
});
const [StoreCardModalComp, StoreCardModalApi] = useVbenModal({
connectedComponent: StoreCardModal,
});
function openStoreCard(storeId?: number) {
if (!storeId) return;
StoreCardModalApi.setData({ storeId: Number(storeId) });
StoreCardModalApi.open();
}
const [PatientsModal, PatientsModalApi] = useVbenModal({
connectedComponent: WxUserPatientsModal,
});
@@ -818,6 +832,7 @@ const openOrderAmountVerify = () => {
</AntdModal>
<RefundModal />
<DoctorCardModals />
<StoreCardModalComp />
<PatientsModal />
<PatientDetailModal />
<ExportModal />
@@ -883,7 +898,18 @@ const openOrderAmountVerify = () => {
<Tag v-else color="default">线下就诊</Tag>
</div>
<div class="font-medium">{{ row.order_no }}</div>
<div class="text-xs text-gray-500">{{ row.store?.name || '—' }}</div>
<div class="text-xs text-gray-500">
<Button
v-if="row.store?.id"
type="link"
size="small"
class="!h-auto !px-0"
@click="openStoreCard(row.store.id)"
>
{{ row.store?.name || '—' }}
</Button>
<span v-else>{{ row.store?.name || '—' }}</span>
</div>
<div class="text-xs text-gray-600">下单时间: {{ row.created_at || '—' }}</div>
</div>
</template>

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
title="退款"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -15,7 +15,7 @@ export const gridOptions: VxeGridProps<RegisterOrderItem> = {
{ field: 'doctor_info.name', title: '开方医生' },
{ field: 'doctor_info.depart.name', title: '科室' },
{ field: 'user_patient.name', title: '就诊人名称' },
{ field: 'store.name', title: '开方诊所' },
{ field: 'store.name', title: '开方诊所', slots: { default: 'store-name' } },
{ field: 'salesperson', title: '推广员', slots: { default: 'salesperson' } },
{ field: 'type', title: '挂号类型', slots: { default: 'type' } },
{ field: 'prescription', title: '处方', width: 240, slots: { default: 'prescription'} },

View File

@@ -10,6 +10,7 @@ import { Button, Image, message, Modal as AntdModal, Tag } from "ant-design-vue"
import { useVbenVxeGrid } from "#/adapter/vxe-table";
import { TableAction } from "#/components/table-action";
import StoreCardModal from "#/components/store-card/StoreCardModal.vue";
import { simulatePayApi } from "#/views/business/order/api/order-ops";
import PrescriptionDetail from "#/views/doctor/doctor-reception/components/PrescriptionDetail.vue";
@@ -38,6 +39,16 @@ const [Grid, gridApi] = useVbenVxeGrid({
gridEvents
});
const [StoreCardModalComp, StoreCardModalApi] = useVbenModal({
connectedComponent: StoreCardModal,
});
function openStoreCard(storeId?: number) {
if (!storeId) return;
StoreCardModalApi.setData({ storeId: Number(storeId) });
StoreCardModalApi.open();
}
const route = useRoute();
onMounted(() => {
@@ -108,6 +119,7 @@ function formatRegisterPrice(price: unknown) {
<Page auto-content-height title="订单管理">
<PrescriptionDetailModal />
<RefundModal />
<StoreCardModalComp />
<Grid>
<template #toolbar-buttons>
<TableAction :actions="[]" :drop-down-actions="[]">
@@ -119,6 +131,18 @@ function formatRegisterPrice(price: unknown) {
</template>
</TableAction>
</template>
<template #store-name="{ row }">
<Button
v-if="row.store?.id || row.store_id"
type="link"
size="small"
class="!px-0"
@click="openStoreCard(row.store?.id || row.store_id)"
>
{{ row.store?.name || "—" }}
</Button>
<span v-else>{{ row.store?.name || "—" }}</span>
</template>
<template #prescription="{ row }">
<div v-for="item in row.prescription" :key="item.id">
<Button type="link" @click="openPrescriptionDetail(item.id)">

View File

@@ -80,7 +80,7 @@ const handleChange = (info: { file: UploadFile }) => {
};
</script>
<template>
<Modal class="w-[30%]" title="上传Excel">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="上传Excel">
<!-- 新增:显示文件选择状态 -->
<div v-if="isFileSelected" class="mb-4 p-3 bg-green-50 border border-green-200 rounded">
<p class="text-green-700 text-sm">已选择文件:{{ selectedFile?.name }}</p>

View File

@@ -123,7 +123,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}中药`"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -102,7 +102,7 @@ const handleChange = (info: { file: UploadFile }) => {
};
</script>
<template>
<Modal class="w-[30%]" title="上传Excel">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="上传Excel">
<!-- 显示文件选择状态提示 -->
<div
v-if="isFileSelected"

View File

@@ -142,7 +142,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}保健食品`"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -73,7 +73,7 @@ const handleChange = (info: { file: UploadFile }) => {
};
</script>
<template>
<Modal class="w-[30%]" title="上传Excel">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="上传Excel">
<div
v-if="isFileSelected"
class="mb-4 rounded border border-green-200 bg-green-50 p-3"

View File

@@ -96,7 +96,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}医疗器械`"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -73,7 +73,7 @@ const handleChange = (info: { file: UploadFile }) => {
};
</script>
<template>
<Modal class="w-[30%]" title="上传Excel">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="上传Excel">
<div
v-if="isFileSelected"
class="mb-4 rounded border border-green-200 bg-green-50 p-3"

View File

@@ -96,7 +96,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}非药品`"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -66,7 +66,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}产品服务包`"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -80,7 +80,7 @@ const handleChange = (info: { file: UploadFile }) => {
};
</script>
<template>
<Modal class="w-[30%]" title="上传Excel">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="上传Excel">
<!-- 新增:显示文件选择状态 -->
<div
v-if="isFileSelected"

View File

@@ -152,7 +152,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}西(中成)药`"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -100,7 +100,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal title="编辑药方" class="w-[80%]">
<Modal title="编辑药方" class="w-[80%] md:w-[50%] lg:w-[30%]">
<div v-if="prescriptionType === 'chinese'">
<ChineseDrugEditor
ref="chineseDrugEditorRef"

View File

@@ -178,7 +178,7 @@ function bindPrescriptionTypeChange() {
</script>
<template>
<Modal :title="`${isUpdate ? '编辑' : '新增'}特色方`" class="w-[80%]">
<Modal :title="`${isUpdate ? '编辑' : '新增'}特色方`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Form />
<div v-if="prescriptionType === 'chinese'" class="mt-4 border-t pt-4">
<ChineseDrugEditor

View File

@@ -58,7 +58,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}轮播图`"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -132,7 +132,7 @@ const exportWarehouseDrugManagementTemplate = () => {
};
</script>
<template>
<Modal :title="importType === 1 ? '上传Excel' : titles" class="w-[30%]">
<Modal :title="importType === 1 ? '上传Excel' : titles" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Button
v-if="importType === 1"
type="link"

View File

@@ -112,7 +112,7 @@ const exportWarehouseDrugManagementStoreTemplate = () => {
<template>
<Modal
:title="importType === 1 ? '上传Excel' : '批量修改价格'"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Button
v-if="importType === 1"

View File

@@ -126,7 +126,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}仓库药品`"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -115,7 +115,7 @@ const exportWarehouseDrugManagementStoreTemplate = () => {
<template>
<Modal
:title="importType === 1 ? '上传Excel' : '批量修改价格'"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Button
v-if="importType === 1"

View File

@@ -126,7 +126,7 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal
:title="`${isUpdate === true ? '编辑' : '新增'}仓库药品`"
class="w-[30%]"
class="w-[80%] md:w-[50%] lg:w-[30%]"
>
<Form />
</Modal>

View File

@@ -1,16 +1,20 @@
<script lang="ts" setup>
/**
* 医生绑定诊所弹窗:使用门店多选搜索(名称/首拼),替代全量 Select
*/
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message, Select } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import StoreMultiSearch from '#/components/form/components/store-multi-search.vue';
import { updateDoctorStoreBindApi } from '#/views/doctor/doctor/api';
import { getStoreOption } from '#/views/system/store/api';
const gridApi = ref();
const data = ref();
const optioinData = ref();
const data = ref<{ su_id?: number; stores?: number[] }>({ stores: [] });
/** 打开时回填已绑门店名称 */
const initialItems = ref<{ id: number; name: string }[]>([]);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
@@ -21,7 +25,7 @@ const [Modal, modalApi] = useVbenModal({
onConfirm: async () => {
updateDoctorStoreBindApi({
doctor_id: data.value.su_id,
store_ids: data.value.stores,
store_ids: data.value.stores || [],
}).then(() => {
modalApi.close();
message.success('保存成功');
@@ -30,41 +34,35 @@ const [Modal, modalApi] = useVbenModal({
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
const { values } = modalApi.getData<Record<string, any>>();
gridApi.value = modalApi.getData()?.gridApi ?? null;
const { values } = modalApi.getData<Record<string, any>>() || {};
if (values) {
// 拷贝 values
data.value = JSON.parse(JSON.stringify(values));
data.value.stores = data.value.stores.map((item: any) => item.store_id);
getStoreOptionFun();
const copied = JSON.parse(JSON.stringify(values));
const storesRaw = copied.stores || [];
initialItems.value = storesRaw.map((item: any) => ({
id: Number(item.store_id ?? item.id),
name: String(item.store?.name || item.name || `门店${item.store_id ?? item.id}`),
}));
data.value = {
su_id: copied.su_id,
stores: storesRaw.map((item: any) => Number(item.store_id ?? item.id)),
};
}
}
},
});
const getStoreOptionFun = () => {
getStoreOption({}).then((res) => {
optioinData.value = res.map((item: any) => {
return {
label: item.name,
value: item.id,
};
});
});
};
</script>
<template>
<!-- <Modal class="w-[60%]" title="西(中成)药列表">-->
<Modal class="w-[60%]" title="绑定诊所">
<div style="width: 60%; margin: 30px auto;">
<Select
<div style="width: 80%; margin: 30px auto">
<StoreMultiSearch
v-if="data"
v-model:value="data.stores"
:options="optioinData"
mode="multiple"
style="width: 100%"
:initial-items="initialItems"
:store-type="0"
placeholder="输入诊所名称或拼音首拼搜索"
/>
</div>
</Modal>
</template>
<style lang="scss"></style>

View File

@@ -1,6 +1,5 @@
import type { VbenFormProps } from '#/adapter/form';
import {getStoreOption} from "#/views/system/store/api";
import {getDoctorTitleOptionApi, getPharmacistTitleOptionApi} from "#/views/doctor/pharmacist/api";
import {getDepartOptionApi} from "#/views/doctor/doctor/api";
@@ -27,25 +26,12 @@ export const modalFormProps: VbenFormProps = {
},
},
{
component: 'ApiSelect',
component: 'StoreMultiSearch',
componentProps: {
allowClear: true,
filterOption: (input: string, option: any) => {
// 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getStoreOption,
placeholder: '请选择',
model: 'multiple',
placeholder: '输入诊所名称或拼音首拼',
storeType: 0,
},
defaultValue: [],
fieldName: 'stores',
formItemClass: 'col-span-6',
label: '诊所',
@@ -113,25 +99,12 @@ export const infoModalFormProps: VbenFormProps = {
rules: 'required',
},
{
component: 'ApiSelect',
component: 'StoreMultiSearch',
componentProps: {
allowClear: true,
filterOption: (input: string, option: any) => {
// 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getStoreOption,
placeholder: '请选择诊所',
model: 'multiple',
placeholder: '输入诊所名称或拼音首拼',
storeType: 0,
},
defaultValue: [],
dependencies: {
show: (values) => {
return values.id == null;

View File

@@ -1,17 +1,21 @@
<script lang="ts" setup>
/**
* 药师绑定诊所弹窗:使用门店多选搜索(名称/首拼),替代全量 Select
*/
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { message, Select } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { getStoreOption } from '#/views/system/store/api';
import StoreMultiSearch from '#/components/form/components/store-multi-search.vue';
import { updatePharmacistStoreBindApi } from '../api';
const gridApi = ref();
const data = ref();
const optioinData = ref();
const data = ref<{ su_id?: number; store_ids?: number[] }>({ store_ids: [] });
/** 打开时回填已绑门店名称 */
const initialItems = ref<{ id: number; name: string }[]>([]);
const [Modal, modalApi] = useVbenModal({
fullscreenButton: false,
@@ -22,7 +26,7 @@ const [Modal, modalApi] = useVbenModal({
onConfirm: async () => {
updatePharmacistStoreBindApi({
doctor_id: data.value.su_id,
store_ids: data.value.store_ids,
store_ids: data.value.store_ids || [],
}).then(() => {
modalApi.close();
message.success('保存成功');
@@ -31,41 +35,35 @@ const [Modal, modalApi] = useVbenModal({
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
const { values } = modalApi.getData<Record<string, any>>();
gridApi.value = modalApi.getData()?.gridApi ?? null;
const { values } = modalApi.getData<Record<string, any>>() || {};
if (values) {
// 拷贝 values
data.value = JSON.parse(JSON.stringify(values));
data.value.store_ids = data.value.bind_stores.map((item: any) => item.id);
getStoreOptionFun();
const copied = JSON.parse(JSON.stringify(values));
const bindStores = copied.bind_stores || [];
initialItems.value = bindStores.map((item: any) => ({
id: Number(item.id),
name: String(item.name || `门店${item.id}`),
}));
data.value = {
su_id: copied.su_id,
store_ids: bindStores.map((item: any) => Number(item.id)),
};
}
}
},
});
const getStoreOptionFun = () => {
getStoreOption({}).then((res) => {
optioinData.value = res.map((item: any) => {
return {
label: item.name,
value: item.id,
};
});
});
};
</script>
<template>
<!-- <Modal class="w-[60%]" title="西(中成)药列表">-->
<Modal class="w-[60%]" title="绑定诊所">
<div style="width: 60%; margin: 30px auto;">
<Select
<div style="width: 80%; margin: 30px auto">
<StoreMultiSearch
v-if="data"
v-model:value="data.store_ids"
:options="optioinData"
mode="multiple"
style="width: 100%"
:initial-items="initialItems"
:store-type="0"
placeholder="输入诊所名称或拼音首拼搜索"
/>
</div>
</Modal>
</template>
<style lang="scss"></style>

View File

@@ -1,6 +1,5 @@
import type { VbenFormProps } from '#/adapter/form';
import {getStoreOption} from "#/views/system/store/api";
import {getPharmacistTitleOptionApi} from "#/views/doctor/pharmacist/api";
export const modalFormProps: VbenFormProps = {
@@ -26,25 +25,12 @@ export const modalFormProps: VbenFormProps = {
},
},
{
component: 'ApiSelect',
component: 'StoreMultiSearch',
componentProps: {
allowClear: true,
filterOption: (input: string, option: any) => {
// 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getStoreOption,
placeholder: '请选择',
model: 'multiple',
placeholder: '输入诊所名称或拼音首拼',
storeType: 0,
},
defaultValue: [],
fieldName: 'stores',
formItemClass: 'col-span-6',
label: '诊所',
@@ -112,25 +98,12 @@ export const infoModalFormProps: VbenFormProps = {
rules: 'required',
},
{
component: 'ApiSelect',
component: 'StoreMultiSearch',
componentProps: {
allowClear: true,
filterOption: (input: string, option: any) => {
// 自定义过滤逻辑,确保可以根据 name 进行搜索
return option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
},
showSearch: true,
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getStoreOption,
placeholder: '请选择诊所',
model: 'multiple',
placeholder: '输入诊所名称或拼音首拼',
storeType: 0,
},
defaultValue: [],
dependencies: {
show: (values) => {
return values.id == null;

View File

@@ -60,6 +60,7 @@ const infoModal = (id) => {
<Tag v-if="row.type === 'refund'" color="purple">出账</Tag>
<Tag v-else-if="row.type === 'enter'" color="green">入账</Tag>
<Tag v-else-if="row.type === 'withdraw'" color="orange">提现</Tag>
<Tag v-else-if="row.type === 'charge'" color="red">代付手续费</Tag>
</div>
</template>
<template #toolbar-tools></template>

View File

@@ -60,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[30%]" title="申请提现">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="申请提现">
<InvalidatedForm />
<SettlementForm />
</Modal>

View File

@@ -1,6 +1,5 @@
import type { VbenFormProps } from '#/adapter/form';
import { getStoreOption } from '#/views/system/store/api';
// import dayjs from 'dayjs';
export const formOptions: VbenFormProps = {
@@ -8,20 +7,13 @@ export const formOptions: VbenFormProps = {
collapsed: false,
schema: [
{
component: 'ApiSelect',
// 门店多选:诊所+药店
component: 'StoreMultiSearch',
componentProps: {
placeholder: '请选择',
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getStoreOption,
placeholder: '输入名称或拼音首拼',
},
// defaultValue: 0,
fieldName: 'store_id',
defaultValue: [],
fieldName: 'store_ids',
label: '诊所',
},
{

View File

@@ -64,7 +64,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal :title="type === 1 ? '作废订单' : '结算订单'" class="w-[30%]">
<Modal :title="type === 1 ? '作废订单' : '结算订单'" class="w-[80%] md:w-[50%] lg:w-[30%]">
<InvalidatedForm v-if="type === 1" />
<SettlementForm v-else />
</Modal>

View File

@@ -1,5 +1,4 @@
import type { VbenFormProps } from '#/adapter/form';
import {getStoreOption} from "#/views/system/store/api";
// import dayjs from 'dayjs';
@@ -8,20 +7,13 @@ export const formOptions: VbenFormProps = {
collapsed: false,
schema: [
{
component: 'ApiSelect',
// 门店多选:诊所+药店
component: 'StoreMultiSearch',
componentProps: {
placeholder: '请选择',
// 菜单接口转options格式
afterFetch: (data: { id: number; name: string }[]) => {
return data.map((item: any) => ({
label: item.name,
value: item.id,
}));
},
api: getStoreOption,
placeholder: '输入名称或拼音首拼',
},
// defaultValue: 0,
fieldName: 'store_id',
defaultValue: [],
fieldName: 'store_ids',
label: '诊所',
},
{

View File

@@ -14,7 +14,6 @@ import {
Button,
Checkbox,
DatePicker,
Input,
RadioGroup,
Select,
Space,
@@ -25,6 +24,7 @@ import {
} from 'ant-design-vue';
import { TableAction } from '#/components/table-action';
import StoreMultiSearch from '#/components/form/components/store-multi-search.vue';
import {
getReconciliationDrugList,
getReconciliationDrugOptions,
@@ -89,7 +89,8 @@ const activeTab = ref('store');
/** 整页共用时间 */
const searchTime = ref<[Dayjs, Dayjs]>([dayjs().startOf('month'), dayjs()]);
const storeName = ref('');
/** 对账诊所 Tab 多选门店(诊所+药店) */
const storeIds = ref<number[]>([]);
const excludeZeroSales = ref(loadExcludeZeroSales());
type ReconciliationSelectOption = { value: number; label: string };
@@ -199,7 +200,7 @@ async function fetchStoreList() {
const res = await getReconciliationStoreList({
page: 1,
pageSize: STORE_LIST_PAGE_SIZE,
store_name: storeName.value,
store_ids: storeIds.value,
search_time: searchTimeParam(),
exclude_zero_sales: isStoreClinicUser.value ? 0 : excludeZeroSales.value,
});
@@ -391,14 +392,11 @@ watch(byOrder, () => {
<Tabs v-model:active-key="activeTab" type="card" :destroy-inactive-tab-pane="false">
<TabPane key="store" tab="诊所">
<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 class="min-w-[260px] flex-1">
<div class="mb-1 text-xs text-muted-foreground">门店</div>
<StoreMultiSearch
v-model:value="storeIds"
placeholder="输入名称或拼音首拼"
/>
</div>
<div v-if="showExcludeZeroSalesFilter">

View File

@@ -60,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal :title="`提现审核【${type === 1 ? '拒绝' : '通过'}】`" class="w-[30%]">
<Modal :title="`提现审核【${type === 1 ? '拒绝' : '通过'}】`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<WithdrawalAuditForm />
</Modal>
</template>

View File

@@ -57,7 +57,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[30%]" title="编辑银行卡账户">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="编辑银行卡账户">
<Form />
</Modal>
</template>

View File

@@ -61,7 +61,7 @@ const setAmount = (amount: number) => {
</script>
<template>
<Modal class="w-[30%]" title="申请提现">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="申请提现">
<Form />
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
<Button type="primary" @click="setAmount(1000)"> 1000 </Button>

View File

@@ -39,7 +39,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%]" title="Api访问日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="Api访问日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 2, xl: 2, lg: 2, md: 2, sm: 2, xs: 2 }"
@@ -50,6 +50,11 @@ const [Modal, modalApi] = useVbenModal({
<span>{{ getApiOpLogOperatorLabel(data) }}</span>
</Descriptions.Item>
<Descriptions.Item label="URL">{{ data.url }}</Descriptions.Item>
<Descriptions.Item label="请求方式">
<span v-if="data.method === 1">GET</span>
<span v-else-if="data.method === 2">POST</span>
<span v-else>未知</span>
</Descriptions.Item>
<Descriptions.Item label="控制器">
{{ data.controller }}
</Descriptions.Item>

View File

@@ -29,10 +29,26 @@ export const formOptions: VbenFormProps = {
fieldName: 'platform_type',
label: '接口来源',
},
{
// 请求方式:与后端 xk_api_op_log.method(1=GET 2=POST)一致
component: 'Select',
componentProps: {
allowClear: true,
options: [
{ label: 'GET', value: 1 },
{ label: 'POST', value: 2 },
],
placeholder: '全部',
},
fieldName: 'method',
label: '请求方式',
},
{
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD', // 确保格式与你需要的一致
format: 'YYYY-MM-DD HH:mm:ss',
showTime: true,
valueFormat: 'YYYY-MM-DD HH:mm:ss',
},
// defaultValue: [dayjs().startOf('month'), dayjs()],
fieldName: 'search_time',

View File

@@ -32,6 +32,12 @@ export const gridOptions: VxeGridProps<RowType> = {
slots: { default: 'admin' },
},
{ field: 'url', title: '访问路由' },
{
field: 'method',
title: '请求方式',
slots: { default: 'method' },
width: 90,
},
{
field: 'platform_type',
title: '接口来源',

View File

@@ -75,6 +75,11 @@ const showModal = (data = {}, isUpdate = false) => {
<template #platform_type="{ row }">
<span>{{ getApiOpLogPlatformLabel(row.platform_type) }}</span>
</template>
<template #method="{ row }">
<Tag v-if="row.method === 1" color="blue">GET</Tag>
<Tag v-else-if="row.method === 2" color="green">POST</Tag>
<Tag v-else color="default">未知</Tag>
</template>
<template #toolbar-tools></template>
<template #equipment="{ row }">
<Icon :icon="getIcon(row.equipment)" :size="20" />

View File

@@ -55,7 +55,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%]" title="监管回调详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="监管回调详情">
<Spin :spinning="loading">
<Descriptions bordered :column="2" class="mb-4">
<Descriptions.Item label="记录ID">{{ data.id }}</Descriptions.Item>

View File

@@ -30,7 +30,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal class="w-[80%]" title="分账日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="分账日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"

View File

@@ -32,7 +32,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%]" title="Api访问日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="Api访问日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"

View File

@@ -30,7 +30,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal class="w-[80%]" title="操作日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="操作日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"

View File

@@ -30,7 +30,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal class="w-[80%]" title="订单日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="订单日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"

View File

@@ -30,7 +30,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal class="w-[80%]" title="处方日志详情">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="处方日志详情">
<div class="flex flex-col gap-4">
<Descriptions
:column="{ xxl: 1, xl: 1, lg: 1, md: 1, sm: 1, xs: 1 }"

View File

@@ -67,7 +67,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal title="拒绝审核" class="w-[30%]">
<Modal title="拒绝审核" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -37,7 +37,7 @@ const [Modal, modalApi] = useVbenModal({
</script>
<template>
<Modal class="w-[80%]" title="处方溯源">
<Modal class="w-[80%] md:w-[50%] lg:w-[30%]" title="处方溯源">
<PrescriptionSourceContent :data="data" />
</Modal>
</template>

View File

@@ -51,7 +51,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`订单:【 ${orderNo} 】发货`" class="w-[30%]">
<Modal :title="`订单:【 ${orderNo} 】发货`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -122,9 +122,9 @@ function openDetail(row: Record<string, any>, scrollToLogistics = false) {
onShip:
Number(row?.warehouse_is_send ?? 0) === 0 || Number(row?.status) === 1
? () => {
detailModalApi.close();
wareSend(row);
}
detailModalApi.close();
wareSend(row);
}
: undefined,
});
detailModalApi.open();
@@ -139,7 +139,7 @@ function productRows(row: Record<string, any>) {
function qtyText(p: Record<string, any>) {
const n = p.number ?? '-';
if (p.dosage != null && p.dosage !== '' && Number(p.dosage) !== 1) {
return `${n}×${p.dosage}`;
return `${n}`;
}
return String(n);
}
@@ -156,159 +156,139 @@ onMounted(() => {
</script>
<template>
<Page auto-content-height title="我的订单">
<FormModal />
<DetailModal />
<div class="flex flex-col gap-4">
<Tabs
:active-key="statusTab"
type="card"
@change="handleTabChange"
>
<Tabs.TabPane key="1" tab="待发货" />
<Tabs.TabPane key="2" tab="待收货" />
<Tabs.TabPane key="7" tab="确认收货" />
</Tabs>
<Page auto-content-height class="bg-[#f7f8fa] dark:bg-[#0a0a0a] transition-colors duration-300" title="我的订单">
<FormModal/>
<DetailModal/>
<div class="flex flex-wrap items-center gap-3">
<Input
v-model:value="orderNo"
allow-clear
class="w-64"
placeholder="输入订单号"
@press-enter="handleSearch"
/>
<Button type="primary" @click="handleSearch">查询</Button>
<span class="text-sm text-[hsl(var(--muted-foreground))]">
当前:{{ tabLabel }}
</span>
<div class="flex flex-col gap-6 p-2 md:p-4">
<!-- 顶部操作栏 -->
<div class="flex flex-col gap-4 rounded-2xl bg-white dark:bg-[#141414] px-6 py-4 shadow-sm dark:shadow-none dark:border dark:border-[#303030] md:flex-row md:items-center md:justify-between transition-colors duration-300">
<Tabs :active-key="statusTab" :tab-bar-style="{ margin: 0, border: 'none' }" @change="handleTabChange" size="large">
<Tabs.TabPane key="1" tab="待发货"/>
<Tabs.TabPane key="2" tab="待收货"/>
<Tabs.TabPane key="7" tab="确认收货"/>
</Tabs>
<Input.Search @search="handleSearch" allow-clear class="w-full md:w-80" enter-button placeholder="请输入订单号快捷查询" size="large" v-model:value="orderNo"/>
</div>
<Spin :spinning="loading">
<div
v-if="list.length"
class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3"
class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3"
>
<Card
v-for="row in list"
:key="row.id"
class="overflow-hidden"
size="small"
>
<template #title>
<div class="flex flex-wrap items-center gap-2">
<span class="font-medium">{{ row.order_no || '—' }}</span>
<Tag :color="statusMeta[row.status]?.color || 'default'">
{{ statusMeta[row.status]?.text || row.status }}
</Tag>
</div>
</template>
<template #extra>
<span class="text-xs text-[hsl(var(--muted-foreground))]">
{{ row.created_at || '' }}
</span>
</template>
<Card :body-style="{ padding: 0 }" :key="row.id" class="overflow-hidden rounded-2xl border-transparent dark:border-[#303030] bg-white dark:bg-[#141414] shadow-sm dark:shadow-none transition-all hover:-translate-y-1 hover:shadow-md dark:hover:border-gray-500" v-for="row in list">
<div class="space-y-2 text-sm">
<div>
<span class="text-[hsl(var(--muted-foreground))]">收件人:</span>
{{ row.express_name || '—' }}
<span class="ml-2">{{ row.express_mobile || '' }}</span>
<!-- 卡片 Header -->
<div class="flex items-center justify-between border-b border-gray-100 dark:border-[#2a2a2a] px-5 py-4 transition-colors duration-300">
<div class="flex items-center gap-2">
<span class="text-sm text-gray-500 dark:text-gray-400">订单号</span>
<span class="text-base font-semibold text-gray-800 dark:text-gray-200">{{ row.order_no || '—' }}</span>
</div>
<div>
<span class="text-[hsl(var(--muted-foreground))]">地址:</span>
{{ addressText(row) }}
</div>
<div>
<div class="mb-1 text-[hsl(var(--muted-foreground))]">
本仓商品:
</div>
<Tag :color="statusMeta[row.status]?.color || 'default'" class="m-0 rounded-full border-none px-3 py-1 font-medium shadow-sm dark:shadow-none">
{{ statusMeta[row.status]?.text || row.status }}
</Tag>
</div>
<!-- 商品展示区 -->
<div class="bg-gray-50/60 dark:bg-[#1a1a1a] px-5 py-4 transition-colors duration-300">
<div v-if="productRows(row).length" class="flex flex-col gap-4">
<div
v-if="productRows(row).length"
class="flex flex-col gap-2"
v-for="p in productRows(row)"
:key="p.id"
class="flex items-start gap-3"
>
<div
v-for="p in productRows(row)"
:key="p.id"
class="flex items-center gap-2"
>
<Image
:src="p.drug?.image || p.drug_image || ''"
:width="40"
:height="40"
class="shrink-0 rounded object-cover"
fallback="/img/empty.png"
/>
<div class="min-w-0 flex-1 text-xs">
<div class="truncate font-medium">
{{ p.drug_name || p.drug?.drug_name || '-' }}
</div>
<div class="text-[hsl(var(--muted-foreground))]">
规格:{{ p.drug?.specification || '-' }}
· 数量:{{ qtyText(p) }}
</div>
<Image :height="68" :src="p.drug?.image || p.drug_image || ''" :width="68" class="shrink-0 rounded-xl border border-gray-100 dark:border-transparent bg-white dark:bg-[#2a2a2a] object-cover p-1 shadow-sm dark:shadow-none" fallback="/img/empty.png"/>
<div class="min-w-0 flex-1 pt-1">
<div class="line-clamp-2 text-sm font-medium text-gray-800 dark:text-gray-200 transition-colors hover:text-blue-600 dark:hover:text-blue-400 cursor-pointer">
{{ p.drug_name || p.drug?.drug_name || '-' }}
</div>
<div class="mt-2 flex items-center justify-between">
<span class="rounded bg-white dark:bg-[#2a2a2a] px-2 py-0.5 text-xs text-gray-500 dark:text-gray-400 shadow-sm dark:shadow-none border border-gray-100 dark:border-transparent">
{{ p.drug?.specification || '默认规格' }}
</span>
<span class="text-base font-bold text-gray-800 dark:text-gray-200">x{{ qtyText(p) }}</span>
</div>
</div>
</div>
<span v-else>—</span>
</div>
<div>
<span class="text-[hsl(var(--muted-foreground))]">物流:</span>
<div v-else class="text-sm text-gray-400 dark:text-gray-600 py-2">— 该订单暂无商品明细 —</div>
</div>
<!-- 配送信息区 -->
<div class="px-5 py-4 text-sm space-y-3 transition-colors duration-300">
<div class="flex items-start">
<span class="w-16 shrink-0 text-gray-400 dark:text-gray-500">收件人</span>
<div class="flex-1 text-gray-800 dark:text-gray-200">
<span class="font-medium mr-2">{{ row.express_name || '—' }}</span>
<span class="text-gray-500 dark:text-gray-400">{{ row.express_mobile || '' }}</span>
<div class="mt-1 text-gray-500 dark:text-gray-400 line-clamp-2">{{ addressText(row) }}</div>
</div>
</div>
<div class="flex items-center">
<span class="w-16 shrink-0 text-gray-400 dark:text-gray-500">物流</span>
<span
:class="
row.logistics_summary === '未发货'
? 'text-[hsl(var(--muted-foreground))]'
: ''
"
class="flex-1 truncate"
:class="row.logistics_summary === '未发货' ? 'text-gray-400 dark:text-gray-500' : 'text-blue-500 dark:text-blue-400 cursor-pointer hover:underline'"
>
{{ row.logistics_summary || '未发货' }}
</span>
</div>
</div>
<template #actions>
<!-- 底部操作区 (已修复 v-if 报错问题) -->
<div class="flex items-center justify-between border-t border-gray-50 dark:border-[#2a2a2a] px-5 py-3 transition-colors duration-300">
<span class="text-xs text-gray-400 dark:text-gray-500">{{ row.created_at || '' }}</span>
<Space>
<Button size="small" type="link" @click="openDetail(row, false)">
详情
<Button @click="openDetail(row, false)" shape="round" size="middle">
订单详情
</Button>
<!-- 修复了此处的 v-if 闭合错误 -->
<Button
v-if="
Number(row.is_send) === 1 ||
Number(row.status) >= 2 ||
!!row.waybill_no
"
size="small"
type="link"
v-if="Number(row.is_send) === 1 || Number(row.status) >= 2 || !!row.waybill_no"
shape="round"
size="middle"
@click="openDetail(row, true)"
>
物流
查看物流
</Button>
<Button
v-if="Number(row.warehouse_is_send ?? 0) === 0"
size="small"
type="link"
class="shadow-md shadow-blue-500/20 dark:shadow-none"
shape="round"
size="middle"
type="primary"
@click="wareSend(row)"
>
发货
立即发货
</Button>
</Space>
</template>
</div>
</Card>
</div>
<Empty v-else class="py-16" :description="`${tabLabel}暂无订单`" />
<Empty :description="`当前暂无${tabLabel}订单`" :image="Empty.PRESENTED_IMAGE_SIMPLE" class="my-20" v-else/>
</Spin>
<div v-if="pager.total > 0" class="flex justify-end">
<Pagination
v-model:current="pager.currentPage"
v-model:page-size="pager.pageSize"
:total="pager.total"
show-size-changer
:page-size-options="['12', '24', '48']"
@change="loadList"
@show-size-change="loadList"
/>
<!-- 分页区域 -->
<div v-if="pager.total > 0" class="flex justify-end pb-8">
<Pagination :page-size-options="['12', '24', '48']" :total="pager.total" @change="loadList" @show-size-change="loadList" show-quick-jumper show-size-changer v-model:current="pager.currentPage" v-model:page-size="pager.pageSize"/>
</div>
</div>
</Page>
</template>
<style scoped>
:deep(.ant-tabs-nav::before) {
border-bottom: none !important;
}
:deep(.ant-tabs-tab) {
@apply dark:text-gray-400;
}
:deep(.ant-tabs-tab-active) {
@apply dark:!text-blue-400;
}
:deep(.ant-card-body) {
@apply dark:bg-[#141414];
}
</style>

View File

@@ -2,7 +2,6 @@ import type { VbenFormProps } from '#/adapter/form';
import { getDepartOptionApi } from '#/views/doctor/doctor/api';
import { getDoctorTitleOptionApi } from '#/views/doctor/pharmacist/api';
import { getStoreOption } from '#/views/system/store/api';
export const modalFormProps: VbenFormProps = {
wrapperClass: 'grid-cols-12',
@@ -25,21 +24,12 @@ export const modalFormProps: VbenFormProps = {
},
},
{
component: 'ApiSelect',
component: 'StoreMultiSearch',
componentProps: {
allowClear: true,
filterOption: (input: string, option: any) =>
option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
showSearch: true,
afterFetch: (data: { id: number; name: string }[]) =>
data.map((item: any) => ({
label: item.name,
value: item.id,
})),
api: getStoreOption,
placeholder: '请选择诊所',
mode: 'multiple',
placeholder: '输入诊所名称或拼音首拼',
storeType: 0,
},
defaultValue: [],
fieldName: 'stores',
formItemClass: 'col-span-12',
label: '关联诊所',

View File

@@ -7,24 +7,15 @@ export const formOptions: VbenFormProps = {
collapsed: false,
schema: [
{
// 药店名称搜索字段
component: 'VbenInput',
// 药店多选:仅搜 type=1,名称/首拼合一
component: 'StoreMultiSearch',
componentProps: {
placeholder: '输入名称',
placeholder: '输入名称或拼音首拼',
storeType: 1,
},
defaultValue: '',
fieldName: 'name',
label: '药店名称', // 已从"诊所名称"改为"药店名称"
},
{
// 药店首字母搜索字段
component: 'VbenInput',
componentProps: {
placeholder: '输入名称',
},
defaultValue: '',
fieldName: 'shouzimu',
label: '药店首字母', // 已从"诊所首字母"改为"药店首字母"
defaultValue: [],
fieldName: 'store_ids',
label: '药店',
},
{
// 联系人手机号搜索字段
@@ -36,7 +27,6 @@ export const formOptions: VbenFormProps = {
fieldName: 'mobile',
label: '联系人手机号',
},
// 注意:已移除类型筛选字段,因为药店管理页面固定显示type=1的数据
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,

View File

@@ -37,6 +37,7 @@ import StoreConfigTogglesCell from '#/views/system/store/components/cells/StoreC
import BindConsultationModal from '#/views/system/store/components/BindConsultationModal.vue';
import DrugPriceModal from '#/views/system/store/components/DrugPriceModal.vue';
import StoreExternalFieldModal from '#/views/system/store/components/StoreExternalFieldModal.vue';
import StoreCardModal from '#/components/store-card/StoreCardModal.vue';
import BankCardReportModal from '#/views/system/store-bank-card/components/BankCardReportModal.vue';
import BankCardStatusModal from '#/views/system/store-bank-card/components/BankCardStatusModal.vue';
import BankCardStoreEditModal from '#/views/system/store-bank-card/components/BankCardStoreEditModal.vue';
@@ -97,6 +98,16 @@ const [QrCodePreviewModal, QrCodePreviewApi] = useVbenModal({
connectedComponent: QrCodePreview,
});
const [StoreCardModalComp, StoreCardModalApi] = useVbenModal({
connectedComponent: StoreCardModal,
});
function openStoreCard(row: Record<string, any>) {
if (!row?.id) return;
StoreCardModalApi.setData({ storeId: Number(row.id) });
StoreCardModalApi.open();
}
// 打开二维码预览弹窗
const openQrCodeModal = (url, title, address) => {
QrCodePreviewApi.setData({
@@ -376,6 +387,7 @@ const batchSyncDrugPrice = () => {
<BankCardReportModalComponent />
<BankCardStatusModalComponent />
<BankCardStoreEditModalComponent />
<StoreCardModalComp />
<QrCodePreviewModal />
<DrugPriceModalComponent />
<BindConsultationModalComponent />
@@ -436,6 +448,7 @@ const batchSyncDrugPrice = () => {
variant="pharmacy"
:on-copy="copyText"
:on-edit-external-field="showStoreExternalFieldModal"
:on-open-store="openStoreCard"
/>
</template>
<template #store_config_1="{ row }">

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}平台`" class="w-[30%]">
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}平台`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}角色`" class="w-[30%]">
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}角色`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Form />
</Modal>
</template>

View File

@@ -56,6 +56,19 @@ export async function getStoreOption(data: any) {
return requestClient.get<any>(`${prefix}option`, { params: data });
}
/**
* 门店关键词搜索(名称/拼音首拼),供多选搜索气泡列表
*/
export async function searchStoreOption(params: {
keyword: string;
type?: 0 | 1;
limit?: number;
}) {
return requestClient.get<{
items: { id: number; name: string; type: number; shouzimu: string }[];
}>(`${prefix}search-option`, { params });
}
/**
* 获取诊所详情
* @param id
@@ -64,6 +77,26 @@ export async function getStoreInfo(id: number) {
return requestClient.get<any>(`${prefix}detail`, { params: { id } });
}
/**
* 门店详情卡片(概览 + 统计 + 医生团队)
*/
export async function getStoreCardApi(params: {
id: number;
search_time?: [string, string];
}) {
return requestClient.get<any>(`${prefix}card`, { params });
}
/**
* 门店详情卡片统计刷新
*/
export async function getStoreCardStatsApi(params: {
id: number;
search_time?: [string, string];
}) {
return requestClient.get<any>(`${prefix}card-stats`, { params });
}
/**
* 新增诊所
* @param data

View File

@@ -87,7 +87,7 @@ const rowImageSrc = (row: DrugItem) => {
</script>
<template>
<Modal title="修改药品价格" class="w-[80%]">
<Modal title="修改药品价格" class="w-[80%] md:w-[50%] lg:w-[30%]">
<div>
<Tabs v-model:activeKey="activeTab">
<TabPane

View File

@@ -12,6 +12,8 @@ defineProps<{
field: 'erp_id' | 'mes_id',
label: string,
) => void;
/** 点击门店名称打开详情卡片 */
onOpenStore?: (row: Record<string, any>) => void;
}>();
function getClinicTypeText(clinicType: number) {
@@ -23,7 +25,15 @@ function getClinicTypeText(clinicType: number) {
<template>
<div class="store-basic-info">
<div class="store-basic-info__name">{{ row.name || '-' }}</div>
<Button
v-if="onOpenStore"
type="link"
class="store-basic-info__name !h-auto !px-0 !py-0 text-left"
@click="onOpenStore(row)"
>
{{ row.name || '-' }}
</Button>
<div v-else class="store-basic-info__name">{{ row.name || '-' }}</div>
<div class="store-basic-info__grid">
<div class="info-item">
<span class="info-item__label">ID</span>

View File

@@ -7,24 +7,15 @@ export const formOptions: VbenFormProps = {
collapsed: false,
schema: [
{
// 诊所名称搜索字段
component: 'VbenInput',
// 诊所多选:仅搜 type=0,名称/首拼合一
component: 'StoreMultiSearch',
componentProps: {
placeholder: '输入名称',
placeholder: '输入名称或拼音首拼',
storeType: 0,
},
defaultValue: '',
fieldName: 'name',
label: '诊所名称',
},
{
// 诊所首字母搜索字段
component: 'VbenInput',
componentProps: {
placeholder: '输入名称',
},
defaultValue: '',
fieldName: 'shouzimu',
label: '诊所首字母',
defaultValue: [],
fieldName: 'store_ids',
label: '诊所',
},
{
// 联系人手机号搜索字段
@@ -36,16 +27,6 @@ export const formOptions: VbenFormProps = {
fieldName: 'mobile',
label: '联系人手机号',
},
// 注意:已移除类型筛选字段,因为诊所管理页面固定显示type=0的数据
// {
// component: 'RangePicker',
// componentProps: {
// format: 'YYYY-MM-DD', // 确保格式与你需要的一致
// },
// // defaultValue: [dayjs().startOf('month'), dayjs()],
// fieldName: 'search_time',
// label: '时间范围',
// },
],
// 控制表单是否显示折叠按钮
showCollapseButton: true,

View File

@@ -39,6 +39,7 @@ import BindConsultationModal from './components/BindConsultationModal.vue';
import BindSpecialPrescriptionDoctorModal from './components/BindSpecialPrescriptionDoctorModal.vue';
import StoreExternalFieldModal from './components/StoreExternalFieldModal.vue';
import SalespersonCommissionDrawer from './components/SalespersonCommissionDrawer.vue';
import StoreCardModal from '#/components/store-card/StoreCardModal.vue';
import BankCardReportModal from '#/views/system/store-bank-card/components/BankCardReportModal.vue';
import BankCardStatusModal from '#/views/system/store-bank-card/components/BankCardStatusModal.vue';
import BankCardStoreEditModal from '#/views/system/store-bank-card/components/BankCardStoreEditModal.vue';
@@ -89,6 +90,17 @@ const [QrCodePreviewModal, QrCodePreviewApi] = useVbenModal({
connectedComponent: QrCodePreview,
});
const [StoreCardModalComp, StoreCardModalApi] = useVbenModal({
connectedComponent: StoreCardModal,
});
/** 打开门店详情卡片 */
function openStoreCard(row: Record<string, any>) {
if (!row?.id) return;
StoreCardModalApi.setData({ storeId: Number(row.id) });
StoreCardModalApi.open();
}
const openQrCodeModal = (url, title, address) => {
QrCodePreviewApi.setData({
// 表单值
@@ -403,6 +415,7 @@ const handleSwitchClinicType = (row: any) => {
<BankCardReportModalComponent />
<BankCardStatusModalComponent />
<BankCardStoreEditModalComponent />
<StoreCardModalComp />
<QrCodePreviewModal />
<DrugPriceModalComponent />
<BindConsultationModalComponent />
@@ -466,6 +479,7 @@ const handleSwitchClinicType = (row: any) => {
variant="clinic"
:on-copy="copyText"
:on-edit-external-field="showStoreExternalFieldModal"
:on-open-store="openStoreCard"
/>
</template>
<template #store_config_1="{ row }">

View File

@@ -54,7 +54,7 @@ const [Modal, modalApi] = useVbenModal({
});
</script>
<template>
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}供应商`" class="w-[30%]">
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}供应商`" class="w-[80%] md:w-[50%] lg:w-[30%]">
<Form />
</Modal>
</template>