医生绑定药房

This commit is contained in:
zoujiandong
2026-09-07 14:29:35 +08:00
parent 3fdd87009a
commit 92b059cbc0
4 changed files with 647 additions and 8 deletions
+67
View File
@@ -0,0 +1,67 @@
import request from '@/utils/request';
/**
* 1. 获取医生绑定的药房列表
* @param {String} doctor_id - 医生 ID
* @returns {Promise}
*/
export function getDoctorPharmacyList(doctor_id) {
return request({
url: `/admin/doctor/${doctor_id}/pharmacy`,
method: 'get',
});
}
/**
* 2. 批量设置医生绑定的药房列表(全量覆盖)
* @param {String} doctor_id - 医生 ID
* @param {Object} data - { pharmacy_ids: string[], default_pharmacy_id?: string }
* @returns {Promise}
*/
export function setDoctorPharmacies(doctor_id, data) {
return request({
url: `/admin/doctor/${doctor_id}/pharmacy`,
method: 'put',
data,
});
}
/**
* 3. 为医生新增单个药房绑定
* @param {String} doctor_id - 医生 ID
* @param {Object} data - { pharmacy_id: string, is_default?: number }
* @returns {Promise}
*/
export function addDoctorPharmacy(doctor_id, data) {
return request({
url: `/admin/doctor/${doctor_id}/pharmacy`,
method: 'post',
data,
});
}
/**
* 4. 解除单个药房绑定
* @param {String} doctor_id - 医生 ID
* @param {String} pharmacy_id - 药房 ID
* @returns {Promise}
*/
export function removeDoctorPharmacy(doctor_id, pharmacy_id) {
return request({
url: `/admin/doctor/${doctor_id}/pharmacy/${pharmacy_id}`,
method: 'delete',
});
}
/**
* 5. 设为医生的默认药房
* @param {String} doctor_id - 医生 ID
* @param {String} pharmacy_id - 药房 ID
* @returns {Promise}
*/
export function setDefaultDoctorPharmacy(doctor_id, pharmacy_id) {
return request({
url: `/admin/doctor/${doctor_id}/pharmacy/default/${pharmacy_id}`,
method: 'put',
});
}
+37 -5
View File
@@ -151,11 +151,37 @@
</a-form-item>
</a-col>
</a-row>
<a-divider />
<div class="titlebox">
<div class="bar"></div>
<div class="name">是否推荐</div>
</div>
<a-divider />
<div class="titlebox">
<div class="bar"></div>
<div class="name">绑定药房</div>
</div>
<a-row :gutter="24" style="margin-top: 35px;">
<a-col :span="24">
<a-form-item field="doctor_pharmacy" label="关联药房:">
<div style="width: 100%">
<a-space wrap v-if="modalForm.doctor_pharmacy_list && modalForm.doctor_pharmacy_list.length > 0">
<a-tag
v-for="item in modalForm.doctor_pharmacy_list"
:key="item.pharmacy_id"
:color="item.is_default === 1 ? 'arcoblue' : 'gray'"
size="medium"
>
<template #icon v-if="item.is_default === 1"><icon-check-circle-fill /></template>
{{ item.pharmacy_name }}
<span v-if="item.is_default === 1" style="margin-left: 4px; font-weight: bold">(默认)</span>
</a-tag>
</a-space>
<span v-else style="color: #86909c">暂未绑定任何药房</span>
</div>
</a-form-item>
</a-col>
</a-row>
<a-divider />
<div class="titlebox">
<div class="bar"></div>
<div class="name">是否推荐</div>
</div>
<a-row :gutter="24" style="margin-top: 35px;">
<a-col :span="12">
<a-form-item field="is_recommend" label="状态:">
@@ -495,6 +521,7 @@ const modalForm = reactive({
cur_doctor_expertise: [],
avatar: 'https://img.applets.igandanyiyuan.com/basic/file/doctor_avatar.png',
bank_card_code: '',
doctor_pharmacy_list: [],
});
const hospital_name = ref('');
watch(() => modalForm.hospital, () => {
@@ -807,6 +834,11 @@ const modalForm = reactive({
}
modalForm.cur_doctor_expertise = arr;
}
if (data.doctor_pharmacy && Array.isArray(data.doctor_pharmacy)) {
modalForm.doctor_pharmacy_list = data.doctor_pharmacy;
} else {
modalForm.doctor_pharmacy_list = [];
}
}
};
const departmentData = ref([]);
@@ -0,0 +1,455 @@
<template>
<a-drawer
v-model:visible="visible"
:title="drawerTitle"
:width="860"
:footer="false"
@cancel="handleClose"
>
<!-- 医生信息摘要卡片 -->
<a-card :bordered="false" class="doctor-summary-card">
<div class="doctor-info-box">
<a-avatar :size="60" shape="circle">
<img v-if="currentDoctor?.avatar" :src="currentDoctor.avatar" alt="avatar" />
<icon-user v-else />
</a-avatar>
<div class="doctor-meta">
<div class="doctor-meta-name">
<span class="name">{{ currentDoctor?.user_name || currentDoctor?.card_name || '医生' }}</span>
<a-tag color="blue" size="small" style="margin-left: 8px">
{{ formatDoctorTitle(currentDoctor?.doctor_title) }}
</a-tag>
</div>
<div class="doctor-meta-desc">
<span><strong>所属医院:</strong>{{ currentDoctor?.hospital_name || '-' }}</span>
<span style="margin-left: 20px"><strong>科室:</strong>{{ currentDoctor?.department_custom_name || '-' }}</span>
<span style="margin-left: 20px"><strong>电话:</strong>{{ currentDoctor?.mobile || currentDoctor?.user?.mobile || '-' }}</span>
</div>
</div>
</div>
</a-card>
<a-divider style="margin: 16px 0" />
<!-- 药房操作工具栏 -->
<div class="pharmacy-action-bar">
<div class="action-title">
<span class="title-text">已绑定的药房列表</span>
<span class="sub-text">(提示:每个医生仅支持设置 1 家默认发药药房)</span>
</div>
<a-button
v-if="hasPermission('admin:doctorPharmacy:add')"
type="primary"
size="small"
@click="openAddModal"
>
<template #icon><icon-plus /></template>
新增药房绑定
</a-button>
</div>
<!-- 绑定药房表格 -->
<a-table
:data="pharmacyList"
:loading="loading"
:pagination="false"
row-key="pharmacy_id"
:scroll="{ x: 800 }"
style="margin-top: 12px"
>
<template #columns>
<a-table-column title="#" :width="50" align="center">
<template #cell="{ rowIndex }">{{ rowIndex + 1 }}</template>
</a-table-column>
<a-table-column title="药房名称" data-index="pharmacy_name" :width="180">
<template #cell="{ record }">
<div style="font-weight: 500">{{ record.pharmacy_name }}</div>
<div style="font-size: 12px; color: #86909c">{{ record.pharmacy_code }}</div>
</template>
</a-table-column>
<a-table-column title="基础邮费" :width="90" align="right">
<template #cell="{ record }">
<span class="price-text">¥{{ Number(record.postage || 0).toFixed(2) }}</span>
</template>
</a-table-column>
<a-table-column title="包邮门槛" :width="110" align="right">
<template #cell="{ record }">
<span v-if="Number(record.free_shipping_threshold) > 0" class="price-text">
¥{{ Number(record.free_shipping_threshold).toFixed(2) }}
</span>
<a-tag v-else size="small" color="blue">不设门槛</a-tag>
</template>
</a-table-column>
<a-table-column title="支持自提" :width="90" align="center">
<template #cell="{ record }">
<a-tag v-if="record.is_pickup === 1" color="green" size="small">支持</a-tag>
<a-tag v-else color="gray" size="small">不支持</a-tag>
</template>
</a-table-column>
<a-table-column title="详细地址" data-index="full_address" :min-width="200">
<template #cell="{ record }">
<a-tooltip :content="record.full_address || record.address || '-'">
<div class="address-cell">
{{ record.full_address || record.address || '-' }}
</div>
</a-tooltip>
</template>
</a-table-column>
<a-table-column title="默认药房" :width="120" align="center">
<template #cell="{ record }">
<a-tag v-if="record.is_default === 1" color="arcoblue" size="small">
<template #icon><icon-check-circle-fill /></template>
默认药房
</a-tag>
<a-button
v-else-if="hasPermission('admin:doctorPharmacy:default')"
type="outline"
size="mini"
:loading="record._defaultLoading"
@click="handleSetDefault(record)"
>
设为默认
</a-button>
<span v-else style="color: #c9cdd4; font-size: 12px">普通药房</span>
</template>
</a-table-column>
<a-table-column title="操作" :width="100" align="center" fixed="right">
<template #cell="{ record }">
<a-popconfirm
v-if="hasPermission('admin:doctorPharmacy:remove')"
content="确定解除该医生与此药房的绑定关系?"
type="warning"
@ok="handleRemove(record)"
>
<a-button type="text" status="danger" size="small">
<template #icon><icon-delete /></template>
解除
</a-button>
</a-popconfirm>
</template>
</a-table-column>
</template>
</a-table>
<!-- 新增药房绑定弹窗 -->
<a-modal
v-model:visible="addModalVisible"
title="新增医生药房绑定"
:width="520"
:ok-loading="addSubmitLoading"
@ok="handleAddSubmit"
@cancel="addModalVisible = false"
>
<a-form :model="addForm" ref="addFormRef" layout="vertical">
<a-form-item
field="pharmacy_id"
label="选择药房"
:rules="[{ required: true, message: '请选择要绑定的药房' }]"
>
<a-select
v-model="addForm.pharmacy_id"
placeholder="搜索并选择药房"
allow-search
:filter-option="false"
@search="searchPharmacies"
style="width: 100%"
>
<a-option
v-for="item in availablePharmacyOptions"
:key="item.pharmacy_id"
:value="item.pharmacy_id"
:label="item.pharmacy_name"
>
<div style="display: flex; justify-content: space-between; align-items: center">
<span>{{ item.pharmacy_name }}</span>
<span style="color: #86909c; font-size: 12px">{{ item.pharmacy_code }}</span>
</div>
</a-option>
</a-select>
</a-form-item>
<a-form-item field="is_default" label="是否设为默认药房">
<a-radio-group v-model="addForm.is_default">
<a-radio :value="1">是(开方时默认推荐)</a-radio>
<a-radio :value="0">否(仅作为可选药房)</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</a-modal>
</a-drawer>
</template>
<script setup>
import { ref, reactive, computed } from 'vue';
import { Message } from '@arco-design/web-vue';
import { usePermissionStore } from '@/store/permission';
import {
getDoctorPharmacyList,
addDoctorPharmacy,
removeDoctorPharmacy,
setDefaultDoctorPharmacy,
} from '@/api/doctor/pharmacy';
import { getPharmacyList } from '@/api/basic/pharmacy';
const permissionStore = usePermissionStore();
// 权限校验
const hasPermission = (permission) => {
const perms = permissionStore.buttonPermissions || [];
return perms.includes('*') || perms.includes(permission);
};
const visible = ref(false);
const loading = ref(false);
const currentDoctor = ref(null);
const pharmacyList = ref([]);
// 弹窗表单状态
const addModalVisible = ref(false);
const addSubmitLoading = ref(false);
const addFormRef = ref(null);
const allPharmacies = ref([]);
const searchKeyword = ref('');
const addForm = reactive({
pharmacy_id: undefined,
is_default: 0,
});
const drawerTitle = computed(() => {
const name = currentDoctor.value?.user_name || currentDoctor.value?.card_name || '';
return name ? `药房配置 - ${name}` : '药房配置';
});
// 格式化医生职称
const formatDoctorTitle = (titleVal) => {
const map = {
1: '主任医师',
2: '主任中医师',
3: '副主任医师',
4: '副主任中医师',
5: '主治医师',
6: '住院医师',
};
return map[titleVal] || '医生';
};
// 过滤掉已绑定的药房并支持关键词搜索
const availablePharmacyOptions = computed(() => {
const boundIds = new Set(pharmacyList.value.map((item) => String(item.pharmacy_id)));
return allPharmacies.value.filter((item) => {
// 过滤掉已绑定的
if (boundIds.has(String(item.pharmacy_id))) return false;
// 关键字搜索
if (!searchKeyword.value) return true;
const kw = searchKeyword.value.toLowerCase();
const nameMatch = item.pharmacy_name && item.pharmacy_name.toLowerCase().includes(kw);
const codeMatch = item.pharmacy_code && item.pharmacy_code.toLowerCase().includes(kw);
return nameMatch || codeMatch;
});
});
// 搜索药房选项
const searchPharmacies = (query) => {
searchKeyword.value = query || '';
};
// 加载全部可用药房备选列表(状态为正常的药房)
const fetchAllPharmacies = async () => {
try {
const res = await getPharmacyList({ status: 1 });
if (res.code === 200 && res.data) {
allPharmacies.value = res.data;
}
} catch (error) {
console.error('加载系统药房列表异常:', error);
}
};
// 获取该医生的药房绑定列表
const fetchDoctorPharmacies = async () => {
if (!currentDoctor.value?.doctor_id) return;
loading.value = true;
try {
const res = await getDoctorPharmacyList(currentDoctor.value.doctor_id);
if (res.code === 200 && res.data) {
pharmacyList.value = res.data.map((item) => ({
...item,
_defaultLoading: false,
}));
} else {
Message.error(res.message || '获取绑定药房列表失败');
}
} catch (error) {
console.error('获取绑定药房失败:', error);
} finally {
loading.value = false;
}
};
// 打开抽屉
const open = async (doctor) => {
currentDoctor.value = doctor;
visible.value = true;
await fetchDoctorPharmacies();
fetchAllPharmacies();
};
// 打开新增绑定弹窗
const openAddModal = () => {
addForm.pharmacy_id = undefined;
// 若当前尚未绑定任何药房,默认勾选设为默认
addForm.is_default = pharmacyList.value.length === 0 ? 1 : 0;
searchKeyword.value = '';
addModalVisible.value = true;
};
// 提交新增绑定
const handleAddSubmit = async () => {
if (!addFormRef.value) return;
const errors = await addFormRef.value.validate();
if (errors) return;
addSubmitLoading.value = true;
try {
const res = await addDoctorPharmacy(currentDoctor.value.doctor_id, {
pharmacy_id: String(addForm.pharmacy_id),
is_default: Number(addForm.is_default),
});
if (res.code === 200) {
Message.success('药房绑定成功');
addModalVisible.value = false;
fetchDoctorPharmacies();
} else {
Message.error(res.message || '新增绑定失败');
}
} catch (error) {
Message.error('新增绑定出现异常');
} finally {
addSubmitLoading.value = false;
}
};
// 设为默认药房
const handleSetDefault = async (record) => {
record._defaultLoading = true;
try {
const res = await setDefaultDoctorPharmacy(
currentDoctor.value.doctor_id,
record.pharmacy_id
);
if (res.code === 200) {
Message.success(`已将【${record.pharmacy_name}】设为默认药房`);
fetchDoctorPharmacies();
} else {
Message.error(res.message || '设置默认药房失败');
}
} catch (error) {
Message.error('设置默认药房异常');
} finally {
record._defaultLoading = false;
}
};
// 解除绑定
const handleRemove = async (record) => {
try {
const res = await removeDoctorPharmacy(
currentDoctor.value.doctor_id,
record.pharmacy_id
);
if (res.code === 200) {
Message.success('已解除药房绑定');
fetchDoctorPharmacies();
} else {
Message.error(res.message || '解除绑定失败');
}
} catch (error) {
Message.error('解除绑定出现异常');
}
};
const handleClose = () => {
visible.value = false;
};
defineExpose({
open,
});
</script>
<style lang="scss" scoped>
.doctor-summary-card {
background-color: #f7f8fa;
border-radius: 6px;
:deep(.arco-card-body) {
padding: 12px 16px;
}
}
.doctor-info-box {
display: flex;
align-items: center;
.doctor-meta {
margin-left: 16px;
flex: 1;
.doctor-meta-name {
display: flex;
align-items: center;
.name {
font-size: 16px;
font-weight: 600;
color: #1d2129;
}
}
.doctor-meta-desc {
margin-top: 6px;
font-size: 13px;
color: #4e5969;
}
}
}
.pharmacy-action-bar {
display: flex;
justify-content: space-between;
align-items: center;
.action-title {
.title-text {
font-size: 15px;
font-weight: 600;
color: #1d2129;
}
.sub-text {
font-size: 12px;
color: #86909c;
margin-left: 6px;
}
}
}
.price-text {
font-weight: 500;
color: #ff5722;
}
.address-cell {
max-width: 260px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
+88 -3
View File
@@ -176,6 +176,8 @@
@click="handleDetail(record)"><icon-book />详情</a-button>
<a-button v-has="'admin:sysDoctorList:edit'" type="text" @click="handleUpdate(record)"><icon-edit />
修改</a-button>
<a-button v-has="'admin:doctorPharmacy:list'" type="text"
@click="handleOpenPharmacyDrawer(record)"><icon-home />药房配置</a-button>
<!-- <a-button v-has="'admin:sysDoctorList:remove'" type="text"
@click="() => { deleteVisible = true; deleteData = [record.doctor_id]; }"><icon-delete /> 删除</a-button> -->
</a-space>
@@ -345,6 +347,50 @@
</a-col>
</a-row>
<a-divider />
<div class="titlebox">
<div class="bar"></div>
<div class="name">绑定药房</div>
</div>
<a-row :gutter="24" style="margin-top: 35px;">
<a-col :span="24">
<a-form-item field="doctor_pharmacy" label="关联药房:">
<a-select
v-if="modalSatus !== 'detail'"
v-model="modalForm.doctor_pharmacy"
multiple
allow-clear
allow-search
placeholder="请选择医生关联的药房(支持多选)"
style="width: 100%"
>
<a-option
v-for="item in pharmacyOptions"
:key="item.pharmacy_id"
:value="String(item.pharmacy_id)"
:label="item.pharmacy_name"
>
{{ item.pharmacy_name }} ({{ item.pharmacy_code }})
</a-option>
</a-select>
<div v-else style="width: 100%">
<a-space wrap v-if="modalForm.doctor_pharmacy_list && modalForm.doctor_pharmacy_list.length > 0">
<a-tag
v-for="item in modalForm.doctor_pharmacy_list"
:key="item.pharmacy_id"
:color="item.is_default === 1 ? 'arcoblue' : 'gray'"
size="medium"
>
<template #icon v-if="item.is_default === 1"><icon-check-circle-fill /></template>
{{ item.pharmacy_name }}
<span v-if="item.is_default === 1" style="margin-left: 4px; font-weight: bold">(默认)</span>
</a-tag>
</a-space>
<span v-else style="color: #86909c">暂未绑定任何药房</span>
</div>
</a-form-item>
</a-col>
</a-row>
<a-divider />
<div class="titlebox">
<div class="bar"></div>
<div class="name">是否推荐</div>
@@ -694,6 +740,9 @@
<div v-else-if="okStatus == 6">确定申请CA证书?</div>
<div v-else-if="okStatus == 7">修改职称或者执业证/资格证,需要先在【四川省互联网医疗服务监管平台】修改备案信息成功后,才可保存 (谨慎操作) 。</div>
</a-modal>
<!-- 医生绑定药房抽屉 -->
<DoctorPharmacyDrawer ref="pharmacyDrawerRef" />
</div>
</template>
@@ -701,6 +750,8 @@
import { reactive, ref, getCurrentInstance, onMounted, nextTick, watch } from 'vue';
import { getDoctorList, addDoctor, removeDoctor, updateDoctor, getDoctorDetail, departmentList, decryptCard, hospitalList, expertiseList, areaList, bankList, decryptBank, exportDoctor } from '@/api/doctor/list';
import { applyCA, updateCA, removeCA, updateSign, applySign } from '@/api/doctor/ca';
import { getPharmacyList } from '@/api/basic/pharmacy';
import DoctorPharmacyDrawer from './components/doctorPharmacyDrawer.vue';
import { ossSign, ossUpload } from '@/api/oss';
import { parseTime } from '@/utils/parseTime';
import { Message } from '@arco-design/web-vue';
@@ -772,7 +823,24 @@ const modalForm = reactive({
cur_doctor_expertise: [],
avatar: 'https://img.applets.igandanyiyuan.com/basic/file/doctor_avatar.png',
bank_card_code: '',
doctor_pharmacy: [],
doctor_pharmacy_list: [],
});
const pharmacyDrawerRef = ref(null);
const pharmacyOptions = ref([]);
const handleOpenPharmacyDrawer = (record) => {
pharmacyDrawerRef.value?.open(record);
};
const loadPharmacyOptions = async () => {
try {
const res = await getPharmacyList({ status: 1 });
if (res.code === 200 && res.data) {
pharmacyOptions.value = res.data;
}
} catch (e) {
console.error('加载药房选项失败:', e);
}
};
const hospital_name = ref('');
watch(() => modalForm.hospital, () => {
if (modalForm.hospital && modalForm.hospital.hospital_name) {
@@ -1098,7 +1166,7 @@ const columns = [
{ title: '状态', dataIndex: 'status', slotName: 'status' },
// { title: '创建时间', dataIndex: 'created_at', slotName: 'created_at' },
{ title: '操作', slotName: 'action', fixed: "right", width: 180 },
{ title: '操作', slotName: 'action', fixed: "right", width: 260 },
];
// Table Data
@@ -1135,6 +1203,8 @@ const handleAdd = () => {
modalForm.sign_image = '';
modalForm.doctor_bank_card = {
}
modalForm.doctor_pharmacy = [];
modalForm.doctor_pharmacy_list = [];
license_cert_list.value = [];
qualification_cert_list.value = [];
@@ -1198,6 +1268,13 @@ const handleDetail = async (record) => {
}
modalForm.cur_doctor_expertise = arr;
}
if (data.doctor_pharmacy && Array.isArray(data.doctor_pharmacy)) {
modalForm.doctor_pharmacy_list = data.doctor_pharmacy;
modalForm.doctor_pharmacy = data.doctor_pharmacy.map((item) => String(item.pharmacy_id));
} else {
modalForm.doctor_pharmacy_list = [];
modalForm.doctor_pharmacy = [];
}
}
};
// 修改
@@ -1266,6 +1343,13 @@ const handleUpdate = async (record) => {
})
modalForm.cur_doctor_expertise = arr;
}
if (data.doctor_pharmacy && Array.isArray(data.doctor_pharmacy)) {
modalForm.doctor_pharmacy_list = data.doctor_pharmacy;
modalForm.doctor_pharmacy = data.doctor_pharmacy.map((item) => String(item.pharmacy_id));
} else {
modalForm.doctor_pharmacy_list = [];
modalForm.doctor_pharmacy = [];
}
}
//await nextTick();
@@ -1386,7 +1470,8 @@ const handleSubmit = (done) => {
bank_card_province_id: modalForm.doctor_bank_card.province_id ? modalForm.doctor_bank_card.province_id : 0,
bank_card_city_id: modalForm.doctor_bank_card.city_id ? modalForm.doctor_bank_card.city_id : 0,
bank_card_county_id: modalForm.doctor_bank_card.county_id ? modalForm.doctor_bank_card.county_id : 0,
bank_id: modalForm.doctor_bank_card.bank_id
bank_id: modalForm.doctor_bank_card.bank_id,
doctor_pharmacy: modalForm.doctor_pharmacy || []
}
if(modalForm.is_transfer_prescription == 1 || modalForm.is_transfer_prescription ==0) {
modalData.is_transfer_prescription = modalForm.is_transfer_prescription;
@@ -1783,7 +1868,7 @@ onMounted(() => {
handlExpertiseList();
handelAreaList("", "", 2);
handleBankList();
loadPharmacyOptions();
});
</script>