Files
xk-admin/apps/web-antd/src/views/system/store/index.vue

732 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts" setup>
import type { VxeGridListeners } from '#/adapter/vxe-table';
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';
import { useClipboard } from '@vueuse/core';
import { Button, Image, message, Modal, Tag, Avatar } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { Icon } from '#/components/icon';
import QrCodePreview from '#/components/modal/QrCodePreview.vue';
import PromoterInfoCard from '#/components/promoter/PromoterInfoCard.vue';
import SensitiveText from '#/components/sensitive-text/SensitiveText.vue';
import { TableAction } from '#/components/table-action';
import { resolveAvatarUrl } from '#/views/system/store-input/utils/inputUser';
import {
batchSyncDrugPriceApi,
deleteStore,
openPcWindowsApiByStore,
openQrCodeApi,
updateAllowInsuranceCategoryApi,
updateClinicTypeApi,
toggleSalespersonSeePriceApi,
updateStoreShippingFree,
updateStoreSeeRateStatus,
updateStoreSubscribeStatus,
updateStoreOrderPricePercentAdjust,
} from './api';
import FormModalDemo from './components/modal.vue';
import StoreBasicInfoCell from './components/cells/StoreBasicInfoCell.vue';
import StoreConfigTogglesCell from './components/cells/StoreConfigTogglesCell.vue';
import DrugPriceModal from './components/DrugPriceModal.vue';
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';
import { getStoreBankCardReportDetail } from '#/views/system/store-bank-card/api';
import { formOptions } from './config/search';
import { gridOptions } from './config/table';
import { canManageStoreBankCard } from '#/views/system/admin/_shared/platform-admin-role';
const userStore = useUserStore();
const router = useRouter();
// 判断是否为平台管理员(user_type === 2)
const isPlatformAdmin = computed(() => {
return userStore?.userInfo?.roles?.user_type === 2;
});
const canManageBankCard = computed(() =>
canManageStoreBankCard(userStore.userInfo),
);
// 跳转到审核页面
const goToAudit = () => {
router.push('/system/store-input/audit');
};
const hasTopTableDropDownActions = ref(false);
const gridEvents: VxeGridListeners<any> = {
checkboxChange() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
checkboxAll() {
// eslint-disable-next-line no-use-before-define
const records = gridApi.grid.getCheckboxRecords();
hasTopTableDropDownActions.value = records.length > 0;
},
};
const [Grid, gridApi] = useVbenVxeGrid({
formOptions,
gridOptions,
gridEvents,
});
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({
// 表单值
values: url,
title,
address,
});
QrCodePreviewApi.open();
};
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: FormModalDemo,
});
const [DrugPriceModalComponent, drugPriceModalApi] = useVbenModal({
connectedComponent: DrugPriceModal,
});
const [BindConsultationModalComponent, bindConsultationModalApi] = useVbenModal({
connectedComponent: BindConsultationModal,
});
const [BindSpecialPrescriptionDoctorModalComponent, bindSpecialPrescriptionDoctorModalApi] =
useVbenModal({
connectedComponent: BindSpecialPrescriptionDoctorModal,
});
const [StoreExternalFieldModalComponent, storeExternalFieldModalApi] = useVbenModal({
connectedComponent: StoreExternalFieldModal,
});
const [BankCardReportModalComponent, bankCardReportModalApi] = useVbenModal({
connectedComponent: BankCardReportModal,
});
const [BankCardStatusModalComponent, bankCardStatusModalApi] = useVbenModal({
connectedComponent: BankCardStatusModal,
});
const [BankCardStoreEditModalComponent, bankCardStoreEditModalApi] = useVbenModal({
connectedComponent: BankCardStoreEditModal,
});
const openBankCardStoreEdit = (row: any) => {
bankCardStoreEditModalApi.setData({
storeId: row.id,
storeName: row.name,
gridApi,
});
bankCardStoreEditModalApi.open();
};
const openBankCardReport = async (row: any, update = false) => {
let report = null;
try {
report = await getStoreBankCardReportDetail(row.id);
} catch {
report = null;
}
const reportStatus = Number(report?.report_status ?? 0);
const useUpdate =
(update || [3, 4].includes(reportStatus)) && Boolean(report?.id);
bankCardReportModalApi.setData({
storeId: row.id,
storeName: row.name,
mode: useUpdate ? 'update' : 'report',
report,
gridApi,
});
bankCardReportModalApi.open();
};
const openBankCardStatus = (row: any) => {
bankCardStatusModalApi.setData({
storeId: row.id,
storeName: row.name,
});
bankCardStatusModalApi.open();
};
function getBankCardReportTagColor(status: number) {
switch (Number(status)) {
case 1:
return 'processing';
case 2:
return 'success';
case 3:
case 4:
return 'error';
default:
return 'default';
}
}
function getBankCardReportTagText(row: any) {
return Number(row.bank_card_report_status) === 0
? '报备'
: row.bank_card_report_status_text || '未报备';
}
function handleBankCardReportColumnClick(row: any) {
if (!canManageBankCard.value) return;
const status = Number(row.bank_card_report_status ?? 0);
if (status === 1 || status === 2) {
openBankCardStatus(row);
return;
}
openBankCardReport(row, status !== 0);
}
const showStoreExternalFieldModal = (
row: any,
field: 'erp_id' | 'mes_id',
label: string,
) => {
storeExternalFieldModalApi.setData({
values: row,
field,
label,
gridApi,
});
storeExternalFieldModalApi.open();
};
const showModal = (data = {}, isUpdate = false) => {
formModalApi.setData({
// 表单值
values: data,
update: isUpdate,
gridApi,
});
formModalApi.open();
};
/**
* 打开修改药品价格模态框
*/
const showDrugPriceModal = (row: any) => {
drugPriceModalApi.setData({
store_id: row.id,
});
drugPriceModalApi.open();
};
/**
* 打开绑定在线复诊配置弹窗
*/
const showBindConsultationModal = (row: any) => {
bindConsultationModalApi.setData({
values: row,
gridApi,
});
bindConsultationModalApi.open();
};
const showBindSpecialPrescriptionDoctorModal = (row: any) => {
bindSpecialPrescriptionDoctorModalApi.setData({
values: row,
gridApi,
});
bindSpecialPrescriptionDoctorModalApi.open();
};
const deleteApi = (row: any) => {
let ids = [];
if (row) {
ids.push(row);
} else {
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
}
deleteStore({ ids }).then(() => {
message.success('删除成功!');
gridApi.query();
});
};
/**
* 开启后台账号
*/
function openPcWindows(id) {
openPcWindowsApiByStore(id).then(() => {
message.success('操作成功!');
gridApi.query();
});
}
/**
* 开启后台账号
*/
function openQrCode(id) {
openQrCodeApi(id).then(() => {
message.success('操作成功!');
gridApi.query();
});
}
function copyText(source) {
copy(source);
message.success('复制成功');
}
const { copy } = useClipboard({ legacy: true });
const salespersonDrawerRef = ref<InstanceType<typeof SalespersonCommissionDrawer>>();
const openSalespersonCommission = (row: { id: number; name: string }) => {
salespersonDrawerRef.value?.open(row);
};
/**
* 更新包邮状态
*/
const updateShippingFree = (id: number) => {
updateStoreShippingFree({ id }).then(() => {
message.success('修改成功!');
gridApi.query();
});
};
/**
* 更新价格波动订阅状态
*/
const updateSubscribe = (id: number) => {
updateStoreSubscribeStatus({ id }).then(() => {
message.success('修改成功!');
gridApi.query();
});
};
/**
* 切换是否可查看毛利率
*/
const updateSeeRate = (id: number) => {
updateStoreSeeRateStatus({ id }).then(() => {
message.success('修改成功!');
gridApi.query();
});
};
const updateAllowInsurance = (id: number) => {
updateAllowInsuranceCategoryApi({ id }).then(() => {
message.success('修改成功!');
gridApi.query();
});
};
const updateSalespersonSeePrice = (storeId: number) => {
toggleSalespersonSeePriceApi({ store_id: storeId }).then(() => {
message.success('修改成功!');
gridApi.query();
});
};
const updateOrderPricePercentAdjust = (id: number) => {
updateStoreOrderPricePercentAdjust({ id }).then(() => {
message.success('修改成功!');
gridApi.query();
});
};
/**
* 批量同步总仓库药品
*/
const batchSyncDrugPrice = () => {
const records = gridApi.grid.getCheckboxRecords();
if (records.length === 0) {
message.warning('请至少选择一条记录!');
return;
}
const ids = records.map((item) => item.id);
batchSyncDrugPriceApi(ids)
.then((res) => {
message.success(`批量同步任务已创建,共 ${res.data.success} 个门店的任务已提交!`);
gridApi.query();
})
.catch(() => {
message.error('批量同步失败!');
});
};
/**
* 切换诊所类型
*/
const handleSwitchClinicType = (row: any) => {
if (!row) return;
const currentType = row.clinic_type || 2; // 默认为中医诊所
const newType = currentType === 1 ? 2 : 1;
const currentTypeText = currentType === 1 ? '西医诊所' : '中医诊所';
const newTypeText = newType === 1 ? '西医诊所' : '中医诊所';
const clinicName = row.name || '该诊所';
Modal.confirm({
title: '确认切换诊所类型',
content: `确定要将【${clinicName}】的诊所类型从【${currentTypeText}】切换为【${newTypeText}】吗?`,
okText: '确定',
cancelText: '取消',
onOk: async () => {
try {
await updateClinicTypeApi({ id: row.id, clinic_type: newType });
message.success(`诊所类型已切换为【${newTypeText}】`);
gridApi.query();
} catch (error) {
console.error('切换诊所类型失败:', error);
message.error('切换失败,请稍后重试');
}
}
});
};
</script>
<template>
<Page auto-content-height title="诊所管理">
<FormModal />
<BankCardReportModalComponent />
<BankCardStatusModalComponent />
<BankCardStoreEditModalComponent />
<StoreCardModalComp />
<QrCodePreviewModal />
<DrugPriceModalComponent />
<BindConsultationModalComponent />
<BindSpecialPrescriptionDoctorModalComponent />
<StoreExternalFieldModalComponent />
<SalespersonCommissionDrawer ref="salespersonDrawerRef" />
<Grid>
<template #toolbar-buttons>
<TableAction
:actions="[
{
label: '新增',
type: 'primary',
icon: 'ant-design:plus-outlined',
auth: ['Super Admin', 'Admin'],
onClick: showModal.bind(null),
},
{
label: '审核',
type: 'default',
icon: 'ant-design:audit-outlined',
auth: ['Super Admin', 'Admin'],
ifShow: isPlatformAdmin,
onClick: goToAudit,
},
]"
:drop-down-actions="[
{
label: '删除',
icon: 'ant-design:delete-outlined',
ifShow: hasTopTableDropDownActions,
auth: ['Super Admin', 'Admin'],
// auth: ['超级诊所', 'sys:user:save'],
popConfirm: {
title: '确定删除吗?',
confirm: deleteApi.bind(null, false),
},
},
{
label: '同步总仓库药品',
icon: 'ant-design:sync-outlined',
ifShow: hasTopTableDropDownActions,
popConfirm: {
title: '确定要同步选中门店的总仓库药品吗?同步将更新价格、上架下架状态,并自动添加新药品。',
confirm: batchSyncDrugPrice,
},
},
]"
>
<template #more>
<Button style="margin-left: 16px">
批量操作
<Icon icon="ant-design:down-outlined" />
</Button>
</template>
</TableAction>
</template>
<template #store_basic_info="{ row }">
<StoreBasicInfoCell
:row="row"
variant="clinic"
:on-copy="copyText"
:on-edit-external-field="showStoreExternalFieldModal"
:on-open-store="openStoreCard"
/>
</template>
<template #store_config_1="{ row }">
<StoreConfigTogglesCell
:row="row"
section="switches"
:can-manage-bank-card="canManageBankCard"
:get-bank-card-report-tag-color="getBankCardReportTagColor"
:get-bank-card-report-tag-text="getBankCardReportTagText"
:on-shipping-free="updateShippingFree"
:on-subscribe="updateSubscribe"
:on-see-rate="updateSeeRate"
:on-order-price-adjust="updateOrderPricePercentAdjust"
:on-bank-card-report="handleBankCardReportColumnClick"
/>
</template>
<template #store_config_2="{ row }">
<StoreConfigTogglesCell
:row="row"
section="business"
show-clinic-type
show-insurance
show-salesperson-see-price
:can-manage-bank-card="canManageBankCard"
:get-bank-card-report-tag-color="getBankCardReportTagColor"
:get-bank-card-report-tag-text="getBankCardReportTagText"
:on-shipping-free="updateShippingFree"
:on-subscribe="updateSubscribe"
:on-see-rate="updateSeeRate"
:on-order-price-adjust="updateOrderPricePercentAdjust"
:on-bank-card-report="handleBankCardReportColumnClick"
:on-clinic-type="handleSwitchClinicType"
:on-allow-insurance="updateAllowInsurance"
:on-salesperson-see-price="updateSalespersonSeePrice"
/>
</template>
<template #sales_admin="{ row }">
<PromoterInfoCard :admin="row.new_admin" size="compact" />
</template>
<template #mobile="{ row }">
<div>
诊所联系人:{{ row.contact }}
<Button type="link" @click="copyText(row.contact)">复制</Button>
</div>
<div>
号码:<SensitiveText :record="row" field="mobile" />
</div>
</template>
<template #qr_code="{ row }">
<div class="qr_code">
<Image
:preview="false"
:src="row.qr_code"
height="30"
width="30"
@click="
openQrCodeModal(
row.qr_code,
row.name,
`${row.province.name}${row.city.name}${row.position}`,
)
"
/>
</div>
</template>
<template #online_consultation_config="{ row }">
<div
v-if="row.is_internet_medical === 1 && row.online_consultation_doctor_name"
class="cursor-pointer hover:text-blue-500"
@click="showBindConsultationModal(row)"
>
<div>负责人:{{ row.online_consultation_doctor_name }}</div>
</div>
<div
v-else-if="row.delegate_store_name || row.online_consultation_doctor_name"
class="cursor-pointer hover:text-blue-500"
@click="showBindConsultationModal(row)"
>
<div v-if="row.delegate_store_name">委托:{{ row.delegate_store_name }}</div>
<div v-if="row.online_consultation_doctor_name">负责人:{{ row.online_consultation_doctor_name }}</div>
</div>
<Button v-else type="link" size="small" @click="showBindConsultationModal(row)">
绑定
</Button>
</template>
<template #special_prescription_config="{ row }">
<div
v-if="row.special_prescription_doctor_id"
class="flex cursor-pointer items-center gap-2 hover:text-blue-500"
@click="showBindSpecialPrescriptionDoctorModal(row)"
>
<Avatar
:size="32"
:src="resolveAvatarUrl(row.special_prescription_doctor_avatar)"
/>
<span>{{ row.special_prescription_doctor_name || '未知医生' }}</span>
</div>
<Button
v-else
type="link"
size="small"
@click="showBindSpecialPrescriptionDoctorModal(row)"
>
绑定
</Button>
</template>
<template #start-time="{ row }">
早:<Tag color="success">{{ row.start_time }}</Tag>
<br />
<br />
晚:<Tag color="error">{{ row.end_time }}</Tag>
</template>
<template #toolbar-tools></template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: '编辑',
type: 'link',
icon: 'uil:edit',
size: 'small',
auth: ['Super Admin', 'Admin'],
onClick: showModal.bind(null, row, true),
},
{
label: '删除',
type: 'link',
icon: 'ant-design:delete-outlined',
size: 'small',
auth: ['Super Admin', 'Admin'],
// auth: ['store', 'sys:role:detail'],
popConfirm: {
title: '确定删除吗?',
confirm: deleteApi.bind(null, row.id),
},
},
]"
:drop-down-actions="[
// {
// label: '编辑',
// type: 'link',
// icon: 'ant-design:delete-outlined',
// size: 'small',
// // auth: ['store', 'sys:role:detail'],
// onClick: showModal.bind(null, row, true),
// },
// {
// label: '删除',
// type: 'link',
// icon: 'ant-design:delete-outlined',
// size: 'small',
// // auth: ['store', 'sys:role:detail'],
// popConfirm: {
// title: '确定删除吗?',
// confirm: deleteApi.bind(null, row.id),
// },
// },
{
label: '开通后台',
type: 'link',
icon: 'uil:edit',
size: 'small',
auth: ['Super Admin', 'Admin'],
// auth: ['store', 'sys:role:detail'],
popConfirm: {
title: '确定开通后台吗?',
confirm: openPcWindows.bind(null, row.id),
},
},
{
label: '生成诊所二维码',
type: 'link',
icon: 'uil:edit',
size: 'small',
auth: ['Super Admin', 'Admin'],
// auth: ['store', 'sys:role:detail'],
popConfirm: {
title: '确定生成诊所二维码吗?',
confirm: openQrCode.bind(null, row.id),
},
},
{
label: '修改药品价格',
type: 'link',
icon: 'ant-design:edit-outlined',
size: 'small',
ifShow: isPlatformAdmin,
onClick: showDrugPriceModal.bind(null, row),
},
{
label: '绑定特色方医生',
type: 'link',
icon: 'ant-design:user-add-outlined',
size: 'small',
auth: ['Super Admin', 'Admin'],
onClick: () => showBindSpecialPrescriptionDoctorModal(row),
},
{
label: '查看卡状态',
type: 'link',
icon: 'ant-design:eye-outlined',
size: 'small',
auth: ['Super Admin', 'Admin'],
onClick: () => openBankCardStatus(row),
},
{
label: '银行卡报备',
type: 'link',
icon: 'ant-design:bank-outlined',
size: 'small',
auth: ['Super Admin', 'Admin'],
onClick: () => openBankCardReport(row, false),
},
{
label: '修改银行卡',
type: 'link',
icon: 'ant-design:credit-card-outlined',
size: 'small',
auth: ['Super Admin', 'Admin'],
onClick: () => openBankCardStoreEdit(row),
},
{
label: '修改报备银行卡',
type: 'link',
icon: 'ant-design:file-protect-outlined',
size: 'small',
auth: ['Super Admin', 'Admin'],
onClick: () => openBankCardReport(row, true),
},
// {
// label: '推广员分成',
// type: 'link',
// icon: 'ant-design:account-book-outlined',
// size: 'small',
// auth: ['Super Admin', 'Admin'],
// onClick: () => openSalespersonCommission(row),
// },
]"
/>
</template>
</Grid>
</Page>
</template>
<style scoped>
.qr_code:hover {
cursor: pointer;
}
</style>