多药房配置

This commit is contained in:
zoujiandong
2026-09-24 09:20:19 +08:00
20 changed files with 3332 additions and 98 deletions
+160
View File
@@ -0,0 +1,160 @@
import request from '@/utils/request';
/**
* 获取药房分页列表
* @param {Object} data - 查询参数及分页参数
* @returns {Promise}
*/
export function getPharmacyPage(data) {
return request({
url: '/admin/basic/pharmacy/page',
method: 'post',
data,
});
}
/**
* 获取药房列表(下拉选择框)
* @param {Object} params - 过滤条件
* @returns {Promise}
*/
export function getPharmacyList(params) {
return request({
url: '/admin/basic/pharmacy/list',
method: 'get',
params,
});
}
/**
* 获取药房详情
* @param {String} pharmacy_id - 药房 ID
* @returns {Promise}
*/
export function getPharmacyDetail(pharmacy_id) {
return request({
url: `/admin/basic/pharmacy/${pharmacy_id}`,
method: 'get',
});
}
/**
* 新增药房
* @param {Object} data - 药房实体参数
* @returns {Promise}
*/
export function addPharmacy(data) {
return request({
url: '/admin/basic/pharmacy',
method: 'post',
data,
});
}
/**
* 修改药房
* @param {String} pharmacy_id - 药房 ID
* @param {Object} data - 药房实体参数
* @returns {Promise}
*/
export function updatePharmacy(pharmacy_id, data) {
return request({
url: `/admin/basic/pharmacy/${pharmacy_id}`,
method: 'put',
data,
});
}
/**
* 切换药房状态(快捷开关)
* @param {String} pharmacy_id - 药房 ID
* @param {Number} status - 0: 禁用, 1: 正常
* @returns {Promise}
*/
export function updatePharmacyStatus(pharmacy_id, status) {
return request({
url: `/admin/basic/pharmacy/status/${pharmacy_id}`,
method: 'put',
data: { status },
});
}
/**
* 删除药房(软删除)
* @param {String} pharmacy_id - 药房 ID
* @returns {Promise}
*/
export function deletePharmacy(pharmacy_id) {
return request({
url: `/admin/basic/pharmacy/${pharmacy_id}`,
method: 'delete',
});
}
/**
* 获取药房绑定的医生列表-分页
* @param {Object} data - { pharmacy_id: string, page?: number, page_size?: number, doctor_name?: string, mobile?: string }
* @returns {Promise}
*/
export function getPharmacyDoctorPage(data) {
return request({
url: '/admin/basic/pharmacy/doctor/page',
method: 'post',
data,
});
}
/**
* 获取药房绑定的医生列表(全部/不分页)
* @param {String} pharmacy_id - 药房 ID
* @returns {Promise}
*/
export function getPharmacyDoctorList(pharmacy_id) {
return request({
url: `/admin/basic/pharmacy/doctor/${pharmacy_id}`,
method: 'get',
});
}
/**
* 导出药房关联医生列表
* @param {Object} data - 请求参数
* @param {1|2|3} data.type - 1:当前筛选导出, 2:选中的数据导出, 3:全部导出
* @param {string} [data.pharmacy_id] - 药房 ID
* @param {string} [data.doctor_name] - 医生姓名
* @param {string} [data.mobile] - 医生手机号
* @param {string} [data.id] - 选中的数据 ID,多条用英文逗号分隔
* @returns {Promise}
*/
export function exportPharmacyDoctor(data) {
return request({
url: '/admin/export/pharmacy/doctor',
method: 'post',
data,
});
}
/**
* 获取药房下线受影响医生列表(预检接口)
* @param {String|Number} pharmacy_id - 药房 ID
* @returns {Promise}
*/
export function getAffectedDoctors(pharmacy_id) {
return request({
url: `/admin/pharmacy/affected-doctors/${pharmacy_id}`,
method: 'get',
});
}
/**
* 批量迁移默认药房并执行下线操作
* @param {Object} data - { source_pharmacy_id, action: 'disable'|'delete', unbind_source: 1|0, transfers: Array<{ doctor_id, target_pharmacy_id }> }
* @returns {Promise}
*/
export function batchTransferAndAction(data) {
return request({
url: '/admin/pharmacy/batch-transfer-and-action',
method: 'post',
data,
});
}
+90
View File
@@ -0,0 +1,90 @@
import request from '@/utils/request';
/**
* 1. 获取医生绑定的药房列表
* @param {String} doctor_id - 医生 ID
* @returns {Promise}
*/
export function getDoctorPharmacyList(doctor_id) {
return request({
url: `/admin/doctor/pharmacy/${doctor_id}`,
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/pharmacy/${doctor_id}`,
method: 'put',
data,
});
}
/**
* 3. 为医生新增单个药房绑定
* @param {Object} data - { doctor_id: string, pharmacy_id: string, is_default?: number }
* @returns {Promise}
*/
export function addDoctorPharmacy(data) {
return request({
url: '/admin/doctor/pharmacy',
method: 'post',
data,
});
}
/**
* 4. 解除单个药房绑定 (DELETE 方式,传入绑定记录 ID)
* @param {String} doctor_pharmacy_id - 绑定记录 ID
* @returns {Promise}
*/
export function removeDoctorPharmacy(doctor_pharmacy_id) {
return request({
url: `/admin/doctor/pharmacy/${doctor_pharmacy_id}`,
method: 'delete',
});
}
/**
* 4.1 快捷解除绑定 (POST 方式,Body 传参)
* @param {Object} data - { doctor_pharmacy_id?: string, doctor_id?: string, pharmacy_id?: string }
* @returns {Promise}
*/
export function unbindDoctorPharmacy(data) {
return request({
url: '/admin/doctor/pharmacy/unbind',
method: 'post',
data,
});
}
/**
* 5. 设为医生的默认药房 (PUT 方式,传入绑定记录 ID)
* @param {String} doctor_pharmacy_id - 绑定记录 ID
* @returns {Promise}
*/
export function setDefaultDoctorPharmacy(doctor_pharmacy_id) {
return request({
url: `/admin/doctor/pharmacy/default/${doctor_pharmacy_id}`,
method: 'put',
});
}
/**
* 5.1 快捷设为默认药房 (PUT 方式,Body 传参)
* @param {Object} data - { doctor_pharmacy_id?: string, doctor_id?: string, pharmacy_id?: string }
* @returns {Promise}
*/
export function setDefaultDoctorPharmacyByBody(data) {
return request({
url: '/admin/doctor/pharmacy/default',
method: 'put',
data,
});
}
+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([]);
+19 -8
View File
@@ -51,24 +51,35 @@
</a-row>
<a-row :gutter="24" >
<a-col :span="12">
<a-form-item field="avatar" label="药店编码:">
<span>{{modalForm.product_pharmacy_code
}} </span>
<a-form-item label="所属药房:">
<span>{{ modalForm.pharmacy_name || modalForm.pharmacy?.pharmacy_name || '-' }}</span>
<span v-if="modalForm.pharmacy_code || modalForm.pharmacy?.pharmacy_code" style="color: #86909c; margin-left: 8px">
({{ modalForm.pharmacy_code || modalForm.pharmacy?.pharmacy_code }})
</span>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="第三方药品编码:">
<span>{{ modalForm.product_pharmacy_code || modalForm.pharmacy?.product_pharmacy_code || '-' }}</span>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" >
<a-col :span="12">
<a-form-item label="药店编码:">
<span>{{ modalForm.pharmacy_code || '-' }}</span>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="idCard" label="处方平台编码:" >
<div class="cardNum">{{modalForm.product_platform_code
}}</div>
<div class="cardNum">{{ modalForm.product_platform_code || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" >
<a-col :span="12">
<a-form-item field="avatar" label="创建时间:">
<span>{{modalForm.created_at
}} </span>
<span>{{ modalForm.created_at || '-' }}</span>
</a-form-item>
</a-col>
</a-row>
+31
View File
@@ -80,6 +80,37 @@
</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 label="药房名称:">
<div class="box">
<span>{{ modalForm.pharmacy_name || modalForm.pharmacy?.pharmacy_name || '-' }}</span>
</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="药房代码:">
<div class="box">{{ modalForm.pharmacy_code || modalForm.pharmacy?.pharmacy_code || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="联系电话:">
<div class="box">{{ modalForm.pharmacy?.telephone || '-' }}</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="药房地址:">
<div class="box">{{ modalForm.pharmacy?.full_address || modalForm.pharmacy?.address || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-divider />
<div class="titlebox" v-if="modalForm.order_product_refund">
<div class="bar"></div>
<div class="name">退款信息</div>
+31
View File
@@ -91,6 +91,37 @@
</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 label="药房名称:">
<div class="box">
<span>{{ modalForm.pharmacy_name || modalForm.pharmacy?.pharmacy_name || '-' }}</span>
</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="药房代码:">
<div class="box">{{ modalForm.pharmacy_code || modalForm.pharmacy?.pharmacy_code || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="联系电话:">
<div class="box">{{ modalForm.pharmacy?.telephone || '-' }}</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="药房地址:">
<div class="box">{{ modalForm.pharmacy?.full_address || modalForm.pharmacy?.address || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-divider />
<div class="titlebox" v-if="modalForm.order_product_refund">
<div class="bar"></div>
<div class="name">退款信息</div>
+39 -8
View File
@@ -67,14 +67,45 @@
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" >
<a-col :span="12">
<a-form-item field="idCard" label="医嘱:" >
<div class="box" >{{ modalForm.doctor_advice }}</div>
</a-form-item>
</a-col>
</a-row>
<a-divider />
<a-row :gutter="24" >
<a-col :span="12">
<a-form-item field="idCard" label="医嘱:" >
<div class="box" >{{ modalForm.doctor_advice }}</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 label="药房名称:">
<div class="box">
<span>{{ modalForm.pharmacy_name || modalForm.pharmacy?.pharmacy_name || '-' }}</span>
</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="药房代码:">
<div class="box">{{ modalForm.pharmacy_code || modalForm.pharmacy?.pharmacy_code || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="联系电话:">
<div class="box">{{ modalForm.pharmacy?.telephone || '-' }}</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="药房地址:">
<div class="box">{{ modalForm.pharmacy?.full_address || modalForm.pharmacy?.address || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-divider />
<div class="titlebox" v-if="modalForm.inquiry_doctor && !modalForm.transfer_prescription_doctor">
<div class="bar"></div>
<div class="name">医生信息</div>
+39 -8
View File
@@ -67,14 +67,45 @@
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" >
<a-col :span="12">
<a-form-item field="idCard" label="医嘱:" >
<div class="box" >{{ modalForm.doctor_advice }}</div>
</a-form-item>
</a-col>
</a-row>
<a-divider />
<a-row :gutter="24" >
<a-col :span="12">
<a-form-item field="idCard" label="医嘱:" >
<div class="box" >{{ modalForm.doctor_advice }}</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 label="药房名称:">
<div class="box">
<span>{{ modalForm.pharmacy_name || modalForm.pharmacy?.pharmacy_name || '-' }}</span>
</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="药房代码:">
<div class="box">{{ modalForm.pharmacy_code || modalForm.pharmacy?.pharmacy_code || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="联系电话:">
<div class="box">{{ modalForm.pharmacy?.telephone || '-' }}</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="药房地址:">
<div class="box">{{ modalForm.pharmacy?.full_address || modalForm.pharmacy?.address || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-divider />
<div class="titlebox" v-if="modalForm.inquiry_doctor && showTransferDoctor ">
<div class="bar"></div>
<div class="name">医生信息</div>
+139 -48
View File
@@ -13,20 +13,55 @@
<a-select :style="{width:'300px'}" allow-search placeholder="请选择药品名称" v-model="modalForm.product_name"
:loading="loading" @change="changeMedince" @search="handList" :disabled="modalSatus=='edit'">
<a-option size="large" style="max-width:400px" v-for="item in medinceList" :key="item.product_platform_id"
:value="item.product_platform_id" :label="item.product_name+'('+item.product_platform_code+')'">
{{item.product_name+'('+item.product_platform_code+')' }}
:value="item.product_platform_id" :label="item.product_name + (item.pharmacy_name || item.pharmacy?.pharmacy_name ? ' [' + (item.pharmacy_name || item.pharmacy?.pharmacy_name) + ']' : '') + '(' + item.product_platform_code + ')'">
<span>{{ item.product_name }}</span>
<span v-if="item.pharmacy_name || item.pharmacy?.pharmacy_name" style="color: #165dff; margin-left: 6px;">[{{ item.pharmacy_name || item.pharmacy?.pharmacy_name }}]</span>
<span style="color: #86909c; margin-left: 4px;">({{ item.product_platform_code }})</span>
</a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="pharmacy_id" label="所属药房:">
<a-select
v-if="modalSatus == 'add'"
v-model="modalForm.pharmacy_id"
placeholder="选择药品后自动匹配药房"
disabled
:style="{ width: '300px' }"
>
<a-option
v-for="item in pharmacyList"
: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 class="box">
<span>{{ modalForm.pharmacy_name || modalForm.pharmacy?.pharmacy_name || '-' }}</span>
<span v-if="modalForm.pharmacy_code || modalForm.pharmacy?.pharmacy_code" style="color: #86909c; margin-left: 8px">
({{ modalForm.pharmacy_code || modalForm.pharmacy?.pharmacy_code }})
</span>
</div>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item field="avatar" label="药品规格:">
<span v-if="modalForm.product_spec">{{modalForm.product_spec}} </span>
<span v-else>选择药品后展示</span>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="avatar" label="生产厂家:">
<span v-if="modalForm.manufacturer">{{modalForm.manufacturer}} </span>
<span v-else>选择药品后展示</span>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24" >
<a-col :span="12">
@@ -44,33 +79,6 @@
</div>
</a-form-item>
</a-col>
</a-row>
<!--<a-row :gutter="24" >
<a-col :span="12">
<a-form-item field="idCard" label="药品价格:" >
<div class="cardNum">{{modalForm.product_price}}元</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="idCard" label="药品类型:" >
<div class="box" >
<div class="cardNum">{{formatProductType(modalForm.product_type)}}</div>
</div>
</a-form-item>
</a-col>
</a-row> -->
<a-row :gutter="24" >
<!-- <a-col :span="12">
<a-form-item field="idCard" label="批准文号:" >
<div class="cardNum">{{modalForm.license_number}}</div>
</a-form-item>
</a-col> -->
<a-col :span="12">
<a-form-item field="avatar" label="生产厂家:">
<span v-if="modalForm.manufacturer">{{modalForm.manufacturer}} </span>
<span v-else>选择药品后展示</span>
</a-form-item>
</a-col>
</a-row>
<!-- <a-row :gutter="24" >
@@ -233,9 +241,10 @@
</a-modal>
</template>
<script setup>
import {ref,toRefs} from 'vue';
import {addSysMedince,editSysMedince,editMedinceStatus,getList,getMedinceDetail} from "@/api/medince/list";
import {formatProductType} from "@/utils/format"
import { ref, toRefs, onMounted, watch } from 'vue';
import { addSysMedince, editSysMedince, editMedinceStatus, getList, getMedinceDetail } from "@/api/medince/list";
import { getPharmacyList } from "@/api/basic/pharmacy";
import { formatProductType } from "@/utils/format"
import { Message } from '@arco-design/web-vue';
const props = defineProps({
// 是否显示
@@ -253,9 +262,75 @@ const props = defineProps({
type: Object,
},
});
const medinceList=ref([]);
const medinceList = ref([]);
const pharmacyList = ref([]);
const loading = ref(false);
const rules={
// 自动匹配并设置所属药房
const matchAndSetPharmacy = (target = {}) => {
if (!target) return;
const pid = target.pharmacy_id ?? target.pharmacy?.pharmacy_id;
const pcode = target.pharmacy_code ?? target.pharmacy?.pharmacy_code;
const pname = target.pharmacy_name ?? target.pharmacy?.pharmacy_name;
let matched = null;
if (pid !== undefined && pid !== null && pid !== '') {
matched = pharmacyList.value.find((p) => String(p.pharmacy_id) === String(pid));
}
if (!matched && pcode) {
const codeStr = String(pcode).trim().toLowerCase();
matched = pharmacyList.value.find(
(p) => p.pharmacy_code && String(p.pharmacy_code).trim().toLowerCase() === codeStr
);
}
if (matched) {
modalForm.value.pharmacy_id = String(matched.pharmacy_id);
modalForm.value.pharmacy_name = matched.pharmacy_name;
modalForm.value.pharmacy_code = matched.pharmacy_code;
} else if (pid !== undefined && pid !== null && pid !== '') {
modalForm.value.pharmacy_id = String(pid);
modalForm.value.pharmacy_name = pname || '';
modalForm.value.pharmacy_code = pcode || '';
if (!pharmacyList.value.some((p) => String(p.pharmacy_id) === String(pid))) {
pharmacyList.value.push({
pharmacy_id: String(pid),
pharmacy_name: modalForm.value.pharmacy_name || '-',
pharmacy_code: modalForm.value.pharmacy_code || '',
});
}
} else if (pcode) {
modalForm.value.pharmacy_code = pcode;
if (pname) modalForm.value.pharmacy_name = pname;
}
};
const fetchPharmacyList = async () => {
try {
const res = await getPharmacyList();
if (res.code === 200) {
pharmacyList.value = res.data || [];
if (modalForm.value && (modalForm.value.pharmacy_id || modalForm.value.pharmacy_code)) {
matchAndSetPharmacy(modalForm.value);
}
}
} catch (e) {
console.error('获取药房列表异常:', e);
}
};
onMounted(() => {
fetchPharmacyList();
});
watch(() => props.modalVisible, (val) => {
if (val && pharmacyList.value.length === 0) {
fetchPharmacyList();
}
});
const rules = {
pharmacy_id: [{ required: true, message: '请先选择药品以匹配所属药房' }],
product_platform_id: [{ required: true, message: '请输入药品名称' }],
product_price: [{ required: true, message: '请输入药品价格' }],
product_platform_code: [{ required: true, message: '请输入处方平台编码' }],
@@ -276,18 +351,22 @@ const {modalVisible,modalForm,modalSatus} = toRefs(props);
const handleClose = () => {
emits('familyVisibleChange', false);
};
const handAdd=async()=>{
const handAdd = async () => {
if (!modalForm.value.pharmacy_id) {
Message.warning('所属药房不能为空,请先选择药品以匹配药房');
return;
}
delete modalForm.value.avatar;
delete modalForm.value.product_id
modalForm.value.product_status=1;
delete modalForm.value.product_id;
modalForm.value.product_status = 1;
const {code}=await addSysMedince(modalForm.value);
if(code==200){
const { code } = await addSysMedince(modalForm.value);
if (code == 200) {
Message.success("添加成功");
handleClose(true);
emits('freshList');
}
}
};
const handEdit=async()=>{
const {code}=await editSysMedince(modalForm.value.product_id,modalForm.value);
if(code==200){
@@ -312,14 +391,14 @@ const handleEditStatus=async(status)=>{
const { code, data, message } = await getMedinceDetail(product_platform_id);
if (code == 200) {
Object.assign(modalForm.value,data);
modalForm.value.stock=data.stock;
Object.assign(modalForm.value, data);
modalForm.value.stock = data.stock;
matchAndSetPharmacy(data);
}
};
const handList= async(value)=>{
loading.value=true;
console.log(value)
console.log(value)
const {code,data}=await getList({
product_name: value,
});
@@ -329,12 +408,24 @@ const handList= async(value)=>{
}
}
const changeMedince = (value) => {
if (!value) {
modalForm.value.product_platform_id = '';
modalForm.value.pharmacy_id = '';
modalForm.value.pharmacy_name = '';
modalForm.value.pharmacy_code = '';
modalForm.value.product_spec = '';
modalForm.value.product_pharmacy_code = '';
modalForm.value.manufacturer = '';
return;
}
let arr = medinceList.value.filter((item) => item.product_platform_id == value);
if (!arr.length) return;
modalForm.value.product_platform_id = arr[0].product_platform_id;
console.log(arr[0]);
modalForm.value.product_spec=arr[0].product_spec;
modalForm.value.product_pharmacy_code=arr[0].product_pharmacy_code;
modalForm.value.manufacturer=arr[0].manufacturer;
modalForm.value.product_spec = arr[0].product_spec;
modalForm.value.product_pharmacy_code = arr[0].product_pharmacy_code;
modalForm.value.manufacturer = arr[0].manufacturer;
matchAndSetPharmacy(arr[0]);
handleDetail(arr[0].product_platform_id);
}
</script>
@@ -0,0 +1,521 @@
<template>
<a-drawer
v-model:visible="visible"
:title="drawerTitle"
:width="1080"
:footer="false"
@cancel="handleClose"
>
<!-- 药房信息摘要卡片 -->
<a-card :bordered="false" class="pharmacy-summary-card">
<div class="pharmacy-info-box">
<div class="pharmacy-icon-box">
<icon-home style="font-size: 28px; color: #165dff" />
</div>
<div class="pharmacy-meta">
<div class="pharmacy-meta-title">
<span class="name">{{ currentPharmacy?.pharmacy_name || '-' }}</span>
<a-tag color="blue" size="small" style="margin-left: 8px">
{{ currentPharmacy?.pharmacy_code || '-' }}
</a-tag>
<a-tag v-if="currentPharmacy?.status === 1" color="green" size="small" style="margin-left: 4px">
运营中
</a-tag>
<a-tag v-else color="red" size="small" style="margin-left: 4px">
已停用
</a-tag>
</div>
<div class="pharmacy-meta-desc">
<span><strong>基础邮费:</strong>¥{{ Number(currentPharmacy?.postage || 0).toFixed(2) }}</span>
<span style="margin-left: 20px">
<strong>包邮门槛:</strong>
{{ Number(currentPharmacy?.free_shipping_threshold) > 0 ? `¥${Number(currentPharmacy.free_shipping_threshold).toFixed(2)}` : '不设门槛' }}
</span>
<span style="margin-left: 20px">
<strong>支持自提:</strong>{{ currentPharmacy?.is_pickup === 1 ? '是' : '否' }}
</span>
<span style="margin-left: 20px">
<strong>联系电话:</strong>{{ currentPharmacy?.telephone || '-' }}
</span>
</div>
<div class="pharmacy-meta-address">
<strong>详细地址:</strong>{{ currentPharmacy?.full_address || currentPharmacy?.address || '-' }}
</div>
</div>
</div>
</a-card>
<a-divider style="margin: 16px 0" />
<!-- 筛选表单 -->
<a-form :model="queryForm" ref="queryFormRef" layout="inline" class="search-form">
<a-form-item field="doctor_name" label="医生姓名">
<a-input
v-model="queryForm.doctor_name"
placeholder="请输入医生姓名"
allow-clear
style="width: 170px"
@press-enter="handleQuery"
/>
</a-form-item>
<a-form-item field="mobile" label="手机号码">
<a-input
v-model="queryForm.mobile"
placeholder="请输入手机号码"
allow-clear
style="width: 170px"
@press-enter="handleQuery"
/>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" size="small" @click="handleQuery">
<template #icon><icon-search /></template>
搜索
</a-button>
<a-button size="small" @click="handleResetQuery">
<template #icon><icon-loop /></template>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
<!-- 工具栏:数据统计与导出操作 -->
<div class="table-toolbar">
<div class="table-toolbar-left">
<span class="table-total-tip">共找到 {{ pager.total }} 位关联医生</span>
</div>
<div class="table-toolbar-right">
<a-dropdown
v-if="hasPermission('admin:doctorPharmacy:export') || hasPermission('admin:sysPharmacy:doctor')"
@select="handleExportCommand"
>
<a-button type="outline" size="small" :loading="exportLoading">
<template #icon><icon-download /></template>
导出医生数据
<icon-down style="margin-left: 4px" />
</a-button>
<template #content>
<a-doption :value="1">
<template #icon><icon-filter /></template>
导出当前筛选数据
</a-doption>
<a-doption :value="2" :disabled="selectedRowKeys.length === 0">
<template #icon><icon-check-square /></template>
导出已选数据 {{ selectedRowKeys.length ? `(${selectedRowKeys.length})` : '' }}
</a-doption>
<a-doption :value="3">
<template #icon><icon-layers /></template>
导出该药房全部医生
</a-doption>
</template>
</a-dropdown>
</div>
</div>
<!-- 医生列表表格 -->
<a-table
:columns="columns"
:data="tableData"
:loading="loading"
:pagination="paginationProps"
row-key="doctor_pharmacy_id"
:row-selection="rowSelection"
v-model:selected-keys="selectedRowKeys"
:scroll="{ x: 860 }"
style="margin-top: 10px"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<!-- 序号 -->
<template #index="{ rowIndex }">
{{ (pager.page - 1) * pager.page_size + rowIndex + 1 }}
</template>
<!-- 医生信息 -->
<template #doctor="{ record }">
<div class="doctor-cell">
<a-avatar :size="36" shape="circle">
<img v-if="record.avatar" :src="record.avatar" alt="avatar" />
<icon-user v-else />
</a-avatar>
<div class="doctor-cell-info">
<div class="doctor-name">{{ record.user_name || '-' }}</div>
<a-tag size="mini" color="blue">{{ record.doctor_title_name || '医生' }}</a-tag>
</div>
</div>
</template>
<!-- 所属医院与科室 -->
<template #hospital="{ record }">
<div>{{ record.hospital_name || '-' }}</div>
<div style="font-size: 12px; color: #86909c">{{ record.department_custom_name || '-' }}</div>
</template>
<!-- 是否默认 -->
<template #is_default="{ record }">
<a-tag v-if="record.is_default === 1" color="arcoblue" size="small">
<template #icon><icon-check-circle-fill /></template>
首选默认
</a-tag>
<a-tag v-else color="gray" size="small">普通关联</a-tag>
</template>
<!-- 操作列 -->
<template #action="{ record }">
<a-space :size="4">
<a-button type="text" size="small" @click="handleViewDoctor(record)">
<template #icon><icon-eye /></template>
档案
</a-button>
<a-popconfirm
v-if="hasPermission('admin:doctorPharmacy:remove')"
content="确定解除该医生与当前药房的绑定关系?"
type="warning"
@ok="handleRemoveBinding(record)"
>
<a-button type="text" status="danger" size="small">
<template #icon><icon-delete /></template>
解绑
</a-button>
</a-popconfirm>
</a-space>
</template>
</a-table>
<!-- 公共医生详情模态弹窗 -->
<DoctorModal
:doctorVisible="doctorModalVisible"
:doctor_id="selectedDoctorId"
@doctorVisibleChange="() => { doctorModalVisible = false; selectedDoctorId = ''; }"
/>
</a-drawer>
</template>
<script setup>
import { ref, reactive, computed } from 'vue';
import { Message } from '@arco-design/web-vue';
import { usePermissionStore } from '@/store/permission';
import { getPharmacyDoctorPage, exportPharmacyDoctor } from '@/api/basic/pharmacy';
import { removeDoctorPharmacy } from '@/api/doctor/pharmacy';
import DoctorModal from '@/components/doctorModal.vue';
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 exportLoading = ref(false);
const currentPharmacy = ref(null);
const tableData = ref([]);
const selectedRowKeys = ref([]);
// 表格多选配置
const rowSelection = reactive({
type: 'checkbox',
showCheckedAll: true,
onlyCurrentPage: false,
});
// 医生弹窗查看状态
const doctorModalVisible = ref(false);
const selectedDoctorId = ref('');
const queryFormRef = ref(null);
const queryForm = reactive({
doctor_name: '',
mobile: '',
});
const pager = reactive({
page: 1,
page_size: 10,
total: 0,
});
const paginationProps = computed(() => ({
total: pager.total,
current: pager.page,
pageSize: pager.page_size,
showTotal: true,
showJumper: true,
showPageSize: true,
pageSizeOptions: [10, 20, 50],
}));
const drawerTitle = computed(() => {
const name = currentPharmacy.value?.pharmacy_name || '';
return name ? `关联医生列表 - ${name}` : '关联医生列表';
});
// 表格列配置
const columns = [
{ title: '#', slotName: 'index', width: 50, align: 'center' },
{ title: '医生信息', slotName: 'doctor', width: 170 },
{ title: '手机号码', dataIndex: 'mobile', width: 130 },
{ title: '执业机构与科室', slotName: 'hospital', minWidth: 180 },
{ title: '默认状态', slotName: 'is_default', width: 110, align: 'center' },
{ title: '绑定时间', dataIndex: 'bind_time', width: 170 },
{ title: '操作', slotName: 'action', width: 180, align: 'center', fixed: 'right' },
];
// 获取药房绑定的医生列表数据
const fetchTableData = async () => {
if (!currentPharmacy.value?.pharmacy_id) return;
loading.value = true;
try {
const params = {
pharmacy_id: String(currentPharmacy.value.pharmacy_id),
page: pager.page,
page_size: pager.page_size,
};
if (queryForm.doctor_name) params.doctor_name = queryForm.doctor_name;
if (queryForm.mobile) params.mobile = queryForm.mobile;
const res = await getPharmacyDoctorPage(params);
if (res.code === 200 && res.data) {
tableData.value = res.data.data || [];
pager.total = res.data.total || 0;
pager.page = res.data.page || 1;
pager.page_size = res.data.page_size || 10;
} else {
Message.error(res.message || '获取药房关联医生列表失败');
}
} catch (error) {
console.error('获取关联医生列表异常:', error);
} finally {
loading.value = false;
}
};
// 搜索
const handleQuery = () => {
pager.page = 1;
fetchTableData();
};
// 重置搜索
const handleResetQuery = () => {
queryForm.doctor_name = '';
queryForm.mobile = '';
pager.page = 1;
selectedRowKeys.value = [];
fetchTableData();
};
// 分页变化
const handlePageChange = (page) => {
pager.page = page;
fetchTableData();
};
const handlePageSizeChange = (pageSize) => {
pager.page_size = pageSize;
pager.page = 1;
fetchTableData();
};
// 查看医生档案
const handleViewDoctor = (record) => {
selectedDoctorId.value = String(record.doctor_id);
doctorModalVisible.value = true;
};
// 解绑医生
const handleRemoveBinding = async (record) => {
try {
const res = await removeDoctorPharmacy(
record.doctor_pharmacy_id || record.pharmacy_id
);
if (res.code === 200) {
Message.success('已解除医生绑定');
if (tableData.value.length === 1 && pager.page > 1) {
pager.page -= 1;
}
fetchTableData();
} else {
Message.error(res.message || '解除绑定失败');
}
} catch (error) {
Message.error('解除绑定出现异常');
}
};
// 通用文件安全下载方法
const triggerDownload = (fileUrl, fileName = '药房关联医生列表') => {
if (!fileUrl) return;
const link = document.createElement('a');
link.href = fileUrl;
link.setAttribute('download', fileName);
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
// 药房关联医生列表导出
const handleExport = async (type) => {
const pharmacyId = currentPharmacy.value?.pharmacy_id;
if (!pharmacyId) {
Message.warning('未能获取当前药房信息');
return;
}
// 场景 2:按选中导出需校验勾选项
if (type === 2 && (!selectedRowKeys.value || selectedRowKeys.value.length === 0)) {
Message.warning('请先勾选需要导出的医生数据');
return;
}
const params = {
type,
pharmacy_id: String(pharmacyId),
};
if (type === 1) {
if (queryForm.doctor_name) params.doctor_name = queryForm.doctor_name.trim();
if (queryForm.mobile) params.mobile = queryForm.mobile.trim();
} else if (type === 2) {
params.id = selectedRowKeys.value.join(',');
}
exportLoading.value = true;
try {
const res = await exportPharmacyDoctor(params);
if (res.code === 200 && res.data) {
Message.success('导出成功,正在下载文件...');
const pharmacyName = currentPharmacy.value?.pharmacy_name || '药房';
triggerDownload(res.data, `${pharmacyName}_关联医生.xlsx`);
} else {
Message.error(res.message || '导出失败,请稍后重试');
}
} catch (error) {
console.error('导出异常:', error);
Message.error('导出请求异常,请联系系统管理员');
} finally {
exportLoading.value = false;
}
};
// 下拉菜单导出命令分发
const handleExportCommand = (value) => {
handleExport(Number(value));
};
// 打开抽屉
const open = (pharmacy) => {
currentPharmacy.value = pharmacy;
queryForm.doctor_name = '';
queryForm.mobile = '';
pager.page = 1;
selectedRowKeys.value = [];
visible.value = true;
fetchTableData();
};
const handleClose = () => {
selectedRowKeys.value = [];
visible.value = false;
};
defineExpose({
open,
});
</script>
<style lang="scss" scoped>
.table-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
padding: 0 2px;
.table-total-tip {
font-size: 13px;
color: #86909c;
}
}
.pharmacy-summary-card {
background-color: #f7f8fa;
border-radius: 6px;
:deep(.arco-card-body) {
padding: 12px 16px;
}
}
.pharmacy-info-box {
display: flex;
align-items: flex-start;
.pharmacy-icon-box {
width: 48px;
height: 48px;
border-radius: 8px;
background-color: #e8f3ff;
display: flex;
justify-content: center;
align-items: center;
margin-right: 14px;
flex-shrink: 0;
}
.pharmacy-meta {
flex: 1;
.pharmacy-meta-title {
display: flex;
align-items: center;
.name {
font-size: 16px;
font-weight: 600;
color: #1d2129;
}
}
.pharmacy-meta-desc {
margin-top: 6px;
font-size: 13px;
color: #4e5969;
}
.pharmacy-meta-address {
margin-top: 4px;
font-size: 13px;
color: #86909c;
}
}
}
.search-form {
:deep(.arco-form-item) {
margin-bottom: 8px;
}
}
.doctor-cell {
display: flex;
align-items: center;
.doctor-cell-info {
margin-left: 10px;
.doctor-name {
font-weight: 500;
color: #1d2129;
margin-bottom: 2px;
}
}
}
</style>
@@ -0,0 +1,416 @@
<template>
<a-modal
v-model:visible="visible"
:title="modalTitle"
:width="760"
:ok-loading="submitLoading"
:ok-text="'保存'"
:cancel-text="'取消'"
:footer="mode !== 'detail'"
@ok="handleSubmit"
@cancel="handleCancel"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
:disabled="mode === 'detail'"
auto-label-width
layout="horizontal"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item field="pharmacy_name" label="药房名称">
<a-input
v-model="formData.pharmacy_name"
placeholder="请输入药房名称"
allow-clear
max-length="50"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="pharmacy_code" label="药房代码">
<a-input
v-model="formData.pharmacy_code"
placeholder="请输入药房代码(如 PHARM_001)"
allow-clear
max-length="50"
:disabled="mode === 'edit'"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item field="postage" label="基础邮费 (元)">
<a-input-number
v-model="formData.postage"
placeholder="请输入基础邮费"
:min="0"
:precision="2"
:step="1"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="free_shipping_threshold" label="包邮门槛 (元)">
<a-input-number
v-model="formData.free_shipping_threshold"
placeholder="0表示不设门槛"
:min="0"
:precision="2"
:step="1"
style="width: 100%"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item field="is_pickup" label="支持自提">
<a-radio-group v-model="formData.is_pickup">
<a-radio :value="1">支持</a-radio>
<a-radio :value="0">不支持</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="status" label="药房状态">
<a-radio-group v-model="formData.status">
<a-radio :value="1">正常</a-radio>
<a-radio :value="0">禁用</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item field="telephone" label="联系电话">
<a-input
v-model="formData.telephone"
placeholder="请输入联系电话"
allow-clear
max-length="20"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="24">
<a-form-item label="所属区域">
<a-space style="width: 100%">
<a-select
v-model="formData.province_id"
placeholder="选择省份"
style="width: 170px"
allow-clear
@change="handleProvinceChange"
@clear="handleProvinceClear"
>
<a-option
v-for="item in provinceList"
:key="item.area_id"
:value="Number(item.area_id)"
:label="item.area_name"
>
{{ item.area_name }}
</a-option>
</a-select>
<a-select
v-model="formData.city_id"
placeholder="选择城市"
style="width: 170px"
allow-clear
:disabled="!formData.province_id"
@change="handleCityChange"
@clear="handleCityClear"
>
<a-option
v-for="item in cityList"
:key="item.area_id"
:value="Number(item.area_id)"
:label="item.area_name"
>
{{ item.area_name }}
</a-option>
</a-select>
<a-select
v-model="formData.county_id"
placeholder="选择区县"
style="width: 170px"
allow-clear
:disabled="!formData.city_id"
>
<a-option
v-for="item in countyList"
:key="item.area_id"
:value="Number(item.area_id)"
:label="item.area_name"
>
{{ item.area_name }}
</a-option>
</a-select>
</a-space>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="24">
<a-form-item field="address" label="详细地址">
<a-textarea
v-model="formData.address"
placeholder="请输入详细地址,如街道、门牌号等"
:auto-size="{ minRows: 2, maxRows: 4 }"
allow-clear
max-length="200"
show-word-limit
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
</template>
<script setup>
import { ref, reactive, computed } from 'vue';
import { Message } from '@arco-design/web-vue';
import { addPharmacy, updatePharmacy, getPharmacyDetail } from '@/api/basic/pharmacy';
import { getAdminAreaList } from '@/api/basic/list';
const emit = defineEmits(['success']);
const visible = ref(false);
const mode = ref('add'); // 'add' | 'edit' | 'detail'
const currentId = ref('');
const submitLoading = ref(false);
const formRef = ref(null);
const provinceList = ref([]);
const cityList = ref([]);
const countyList = ref([]);
const defaultFormData = {
pharmacy_name: '',
pharmacy_code: '',
postage: 0,
free_shipping_threshold: 0,
is_pickup: 0,
telephone: '',
province_id: undefined,
city_id: undefined,
county_id: undefined,
address: '',
status: 1,
};
const formData = reactive({ ...defaultFormData });
const modalTitle = computed(() => {
if (mode.value === 'add') return '新增药房';
if (mode.value === 'edit') return '修改药房';
return '药房详情';
});
const rules = {
pharmacy_name: [
{ required: true, message: '请输入药房名称' },
{ maxLength: 50, message: '药房名称不能超过50个字符' },
],
pharmacy_code: [
{ required: true, message: '请输入药房代码' },
{ maxLength: 50, message: '药房代码不能超过50个字符' },
],
postage: [
{ required: true, message: '请输入基础邮费' },
],
free_shipping_threshold: [
{ required: true, message: '请输入满额包邮门槛金额' },
],
is_pickup: [
{ required: true, message: '请选择是否支持自提' },
],
address: [
{ required: true, message: '请输入详细地址' },
{ maxLength: 200, message: '详细地址不能超过200个字符' },
],
};
// 获取区域列表 (area_type: 2 省份, 3 城市, 4 区县)
const fetchAreaList = async (parentId = '', areaType = 2) => {
try {
const res = await getAdminAreaList({
parent_id: parentId,
area_type: areaType,
});
if (res.code === 200) {
if (areaType === 2) {
provinceList.value = res.data || [];
} else if (areaType === 3) {
cityList.value = res.data || [];
} else if (areaType === 4) {
countyList.value = res.data || [];
}
}
} catch (error) {
console.error('获取区域失败:', error);
}
};
const handleProvinceChange = (value) => {
formData.city_id = undefined;
formData.county_id = undefined;
cityList.value = [];
countyList.value = [];
if (value) {
fetchAreaList(value, 3);
}
};
const handleProvinceClear = () => {
formData.province_id = undefined;
formData.city_id = undefined;
formData.county_id = undefined;
cityList.value = [];
countyList.value = [];
};
const handleCityChange = (value) => {
formData.county_id = undefined;
countyList.value = [];
if (value) {
fetchAreaList(value, 4);
}
};
const handleCityClear = () => {
formData.city_id = undefined;
formData.county_id = undefined;
countyList.value = [];
};
const resetForm = () => {
Object.assign(formData, defaultFormData);
cityList.value = [];
countyList.value = [];
if (formRef.value) {
formRef.value.resetFields();
}
};
// 打开弹窗
const open = async (openMode = 'add', id = '') => {
mode.value = openMode;
currentId.value = id;
resetForm();
visible.value = true;
// 保证省份数据已加载
if (provinceList.value.length === 0) {
await fetchAreaList('', 2);
}
if ((openMode === 'edit' || openMode === 'detail') && id) {
try {
const res = await getPharmacyDetail(id);
if (res.code === 200 && res.data) {
const d = res.data;
Object.assign(formData, {
pharmacy_name: d.pharmacy_name || '',
pharmacy_code: d.pharmacy_code || '',
postage: Number(d.postage) || 0,
free_shipping_threshold: Number(d.free_shipping_threshold) || 0,
is_pickup: d.is_pickup ?? 0,
telephone: d.telephone || '',
province_id: d.province_id ? Number(d.province_id) : undefined,
city_id: d.city_id ? Number(d.city_id) : undefined,
county_id: d.county_id ? Number(d.county_id) : undefined,
address: d.address || '',
status: d.status ?? 1,
});
// 联动加载已有城市与区县选项
if (formData.province_id) {
await fetchAreaList(formData.province_id, 3);
}
if (formData.city_id) {
await fetchAreaList(formData.city_id, 4);
}
} else {
Message.error(res.message || '获取药房详情失败');
}
} catch (err) {
Message.error('获取药房详情出现异常');
}
}
};
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return;
const errors = await formRef.value.validate();
if (errors) {
return;
}
const payload = {
pharmacy_name: formData.pharmacy_name,
pharmacy_code: formData.pharmacy_code,
postage: Number(formData.postage),
free_shipping_threshold: Number(formData.free_shipping_threshold),
is_pickup: Number(formData.is_pickup),
telephone: formData.telephone || '',
province_id: formData.province_id ? Number(formData.province_id) : undefined,
city_id: formData.city_id ? Number(formData.city_id) : undefined,
county_id: formData.county_id ? Number(formData.county_id) : undefined,
address: formData.address,
status: Number(formData.status),
};
submitLoading.value = true;
try {
let res;
if (mode.value === 'add') {
res = await addPharmacy(payload);
} else {
res = await updatePharmacy(currentId.value, payload);
}
if (res.code === 200) {
Message.success(mode.value === 'add' ? '添加药房成功' : '修改药房成功');
visible.value = false;
emit('success');
} else {
Message.error(res.message || '操作失败');
}
} catch (error) {
console.error('提交药房表单异常:', error);
} finally {
submitLoading.value = false;
}
};
const handleCancel = () => {
visible.value = false;
resetForm();
};
defineExpose({
open,
});
</script>
<style scoped>
:deep(.arco-form-item) {
margin-bottom: 20px;
}
</style>
@@ -0,0 +1,407 @@
<template>
<a-drawer
v-model:visible="visible"
:title="drawerTitle"
:width="920"
:footer="false"
:mask-closable="false"
@cancel="handleClose"
>
<!-- 顶部警告提示 -->
<a-alert type="warning" style="margin-bottom: 16px">
<template #icon><icon-exclamation-circle-fill /></template>
当前【<strong>{{ sourcePharmacy?.pharmacy_name }}</strong>】共有
<span style="color: #f53f3f; font-weight: bold">{{ affectedDoctorCount }}</span>
位医生的首选默认药房受到影响。
为保障处方业务正常运转,请为他们指定新的默认药房;确认后系统将自动完成迁移并执行<strong>{{ actionText }}</strong>。
</a-alert>
<!-- 批量快捷配置工具栏 -->
<div class="batch-toolbar">
<a-space>
<span class="batch-label">批量操作:</span>
<a-select
v-model="batchTargetPharmacyId"
placeholder="选择统一替代药房"
allow-search
style="width: 220px"
size="small"
>
<a-option
v-for="item in availablePharmacies"
:key="item.pharmacy_id"
:value="String(item.pharmacy_id)"
:label="item.pharmacy_name"
>
{{ item.pharmacy_name }}
</a-option>
</a-select>
<a-button
type="primary"
size="small"
:disabled="selectedRowKeys.length === 0 || !batchTargetPharmacyId"
@click="handleApplyBatch"
>
应用到已选医生 ({{ selectedRowKeys.length }})
</a-button>
<a-button
type="outline"
size="small"
:disabled="!batchTargetPharmacyId"
@click="handleApplyAll"
>
应用到全部医生 ({{ doctorList.length }})
</a-button>
</a-space>
</div>
<!-- 医生分流配置表格 -->
<a-table
:columns="columns"
:data="doctorList"
:loading="loading"
:pagination="false"
row-key="doctor_id"
:row-selection="rowSelection"
v-model:selected-keys="selectedRowKeys"
:scroll="{ x: 800, y: 460 }"
style="margin-top: 12px"
>
<!-- 医生信息 -->
<template #doctor="{ record }">
<div class="doctor-cell">
<div class="doctor-name">{{ record.user_name || '-' }}</div>
<a-tag size="mini" color="blue">{{ record.doctor_title_name || '医生' }}</a-tag>
<div class="doctor-phone">{{ record.mobile || '-' }}</div>
</div>
</template>
<!-- 医院与科室 -->
<template #hospital="{ record }">
<div>{{ record.hospital_name || '-' }}</div>
<div style="font-size: 12px; color: #86909c">{{ record.department_custom_name || '-' }}</div>
</template>
<!-- 已绑定的其他药房 (快捷点选) -->
<template #other_pharmacies="{ record }">
<div v-if="record.other_bound_pharmacies && record.other_bound_pharmacies.length > 0" class="tag-group">
<a-tooltip
v-for="item in record.other_bound_pharmacies"
:key="item.pharmacy_id"
content="点击直接选为新默认药房"
>
<a-tag
color="arcoblue"
size="small"
class="clickable-tag"
:class="{ 'tag-active': String(record.target_pharmacy_id) === String(item.pharmacy_id) }"
@click="handleQuickSelect(record, item.pharmacy_id)"
>
<template #icon><icon-swap /></template>
{{ item.pharmacy_name }}
</a-tag>
</a-tooltip>
</div>
<span v-else style="color: #c9cdd4; font-size: 13px">无其他绑定药房</span>
</template>
<!-- 新默认药房 (必选) -->
<template #target_pharmacy="{ record }">
<a-select
v-model="record.target_pharmacy_id"
placeholder="请指定新默认药房"
allow-search
size="small"
:error="!record.target_pharmacy_id"
style="width: 100%"
>
<a-option
v-for="item in availablePharmacies"
:key="item.pharmacy_id"
:value="String(item.pharmacy_id)"
:label="item.pharmacy_name"
>
{{ item.pharmacy_name }}
</a-option>
</a-select>
</template>
</a-table>
<!-- 抽屉底部动作栏 -->
<div class="drawer-footer">
<div class="footer-left">
<a-checkbox
v-model="unbindSource"
:disabled="actionType === 'delete'"
>
同时解除医生与当前原药房的绑定关系
</a-checkbox>
<span v-if="actionType === 'delete'" class="delete-tip">(删除药房将强制彻底解绑)</span>
</div>
<div class="footer-right">
<a-space>
<a-button @click="handleClose">取消</a-button>
<a-button
type="primary"
:status="actionType === 'delete' ? 'danger' : 'warning'"
:loading="submitLoading"
:disabled="!isAllConfigured"
@click="handleSubmit"
>
确认迁移并{{ actionText }}
<template v-if="!isAllConfigured">
({{ unconfiguredCount }}位未指定)
</template>
</a-button>
</a-space>
</div>
</div>
</a-drawer>
</template>
<script setup>
import { ref, reactive, computed } from 'vue';
import { Message } from '@arco-design/web-vue';
import { getPharmacyList, getAffectedDoctors, batchTransferAndAction } from '@/api/basic/pharmacy';
const emit = defineEmits(['success']);
const visible = ref(false);
const loading = ref(false);
const submitLoading = ref(false);
const sourcePharmacy = ref(null);
const actionType = ref('disable'); // 'disable' | 'delete'
const unbindSource = ref(true);
const doctorList = ref([]);
const selectedRowKeys = ref([]);
const batchTargetPharmacyId = ref('');
const pharmacyOptions = ref([]);
// 过滤掉当前药房自身的目标药房候选列表
const availablePharmacies = computed(() => {
const currentId = String(sourcePharmacy.value?.pharmacy_id || '');
return pharmacyOptions.value.filter((p) => String(p.pharmacy_id) !== currentId);
});
const actionText = computed(() => (actionType.value === 'delete' ? '删除药房' : '禁用药房'));
const drawerTitle = computed(() => `处理受影响医生默认药房(${actionText.value}前置迁移)`);
const affectedDoctorCount = computed(() => doctorList.value.length);
// 校验未指定新药房的医生数量
const unconfiguredCount = computed(() => {
return doctorList.value.filter((d) => !d.target_pharmacy_id).length;
});
const isAllConfigured = computed(() => {
return doctorList.value.length > 0 && unconfiguredCount.value === 0;
});
const rowSelection = reactive({
type: 'checkbox',
showCheckedAll: true,
});
const columns = [
{ title: '医生信息', slotName: 'doctor', width: 170 },
{ title: '执业机构/科室', slotName: 'hospital', width: 180 },
{ title: '医生已绑定其他药房(可快捷点选)', slotName: 'other_pharmacies', minWidth: 230 },
{ title: '新默认药房(必选)', slotName: 'target_pharmacy', width: 220 },
];
// 获取正常启用状态的药房列表
const fetchPharmacyOptions = async () => {
try {
const res = await getPharmacyList({ status: 1 });
if (res.code === 200 && res.data) {
pharmacyOptions.value = Array.isArray(res.data) ? res.data : (res.data.list || []);
}
} catch (err) {
console.error('获取替代药房列表失败:', err);
}
};
// 快捷点选已有药房
const handleQuickSelect = (record, pharmacyId) => {
record.target_pharmacy_id = String(pharmacyId);
};
// 批量应用到已选医生
const handleApplyBatch = () => {
if (!batchTargetPharmacyId.value) return;
doctorList.value.forEach((d) => {
if (selectedRowKeys.value.includes(d.doctor_id)) {
d.target_pharmacy_id = batchTargetPharmacyId.value;
}
});
Message.success(`已为选中的 ${selectedRowKeys.value.length} 位医生设置替代药房`);
};
// 批量应用到全部受影响医生
const handleApplyAll = () => {
if (!batchTargetPharmacyId.value) return;
doctorList.value.forEach((d) => {
d.target_pharmacy_id = batchTargetPharmacyId.value;
});
Message.success(`已为全部 ${doctorList.value.length} 位医生设置替代药房`);
};
// 初始化受影响医生列表(若只绑定了另外一家药房则智能预选)
const initDoctorList = (list) => {
doctorList.value = list.map((item) => ({
...item,
target_pharmacy_id: item.other_bound_pharmacies?.length === 1
? String(item.other_bound_pharmacies[0].pharmacy_id)
: '',
}));
};
// 打开抽屉主入口
const open = async ({ pharmacy, action = 'disable', affectedData = null }) => {
sourcePharmacy.value = pharmacy;
actionType.value = action;
unbindSource.value = true;
selectedRowKeys.value = [];
batchTargetPharmacyId.value = '';
visible.value = true;
fetchPharmacyOptions();
if (affectedData?.doctor_list) {
initDoctorList(affectedData.doctor_list);
} else {
loading.value = true;
try {
const res = await getAffectedDoctors(pharmacy.pharmacy_id);
if (res.code === 200 && res.data) {
initDoctorList(res.data.doctor_list || []);
} else {
Message.error(res.message || '获取受影响医生列表失败');
}
} catch (e) {
Message.error('获取受影响医生列表异常');
} finally {
loading.value = false;
}
}
};
const handleClose = () => {
visible.value = false;
};
// 提交迁移并执行下线
const handleSubmit = async () => {
if (!isAllConfigured.value) {
Message.warning(`尚有 ${unconfiguredCount.value} 位医生未指定新的默认药房`);
return;
}
const payload = {
source_pharmacy_id: String(sourcePharmacy.value.pharmacy_id),
action: actionType.value,
unbind_source: actionType.value === 'delete' ? 1 : (unbindSource.value ? 1 : 0),
transfers: doctorList.value.map((d) => ({
doctor_id: String(d.doctor_id),
target_pharmacy_id: String(d.target_pharmacy_id),
})),
};
submitLoading.value = true;
try {
const res = await batchTransferAndAction(payload);
if (res.code === 200) {
Message.success(`医生分流完成,药房已成功${actionText.value}`);
visible.value = false;
emit('success', { action: actionType.value, pharmacy_id: payload.source_pharmacy_id });
} else {
Message.error(res.message || '迁移下线操作失败');
}
} catch (err) {
Message.error('迁移下线请求异常');
} finally {
submitLoading.value = false;
}
};
defineExpose({
open,
});
</script>
<style lang="scss" scoped>
.batch-toolbar {
background-color: #f7f8fa;
padding: 10px 14px;
border-radius: 6px;
display: flex;
align-items: center;
.batch-label {
font-size: 13px;
font-weight: 500;
color: #4e5969;
}
}
.doctor-cell {
.doctor-name {
font-weight: 500;
color: #1d2129;
margin-bottom: 2px;
}
.doctor-phone {
font-size: 12px;
color: #86909c;
margin-top: 2px;
}
}
.tag-group {
display: flex;
flex-wrap: wrap;
gap: 6px;
.clickable-tag {
cursor: pointer;
transition: all 0.2s;
user-select: none;
&:hover {
opacity: 0.85;
transform: translateY(-1px);
}
}
.tag-active {
background-color: #165dff !important;
color: #fff !important;
font-weight: 500;
}
}
.drawer-footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background-color: #fff;
border-top: 1px solid #e5e6eb;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 10;
.footer-left {
display: flex;
align-items: center;
.delete-tip {
font-size: 12px;
color: #86909c;
margin-left: 4px;
}
}
}
</style>
+620
View File
@@ -0,0 +1,620 @@
<template>
<div class="app-container">
<!-- 搜索筛选表单 -->
<a-form :model="queryForm" ref="queryFormRef" layout="inline" class="search-form">
<a-form-item field="pharmacy_name" label="药房名称">
<a-input
v-model="queryForm.pharmacy_name"
placeholder="请输入药房名称"
allow-clear
style="width: 180px"
@press-enter="handleQuery"
/>
</a-form-item>
<a-form-item field="pharmacy_code" label="药房代码">
<a-input
v-model="queryForm.pharmacy_code"
placeholder="请输入药房代码"
allow-clear
style="width: 180px"
@press-enter="handleQuery"
/>
</a-form-item>
<a-form-item field="status" label="状态">
<a-select
v-model="queryForm.status"
placeholder="全部状态"
allow-clear
style="width: 140px"
>
<a-option :value="1">正常</a-option>
<a-option :value="0">禁用</a-option>
</a-select>
</a-form-item>
<a-form-item field="is_pickup" label="支持自提">
<a-select
v-model="queryForm.is_pickup"
placeholder="全部"
allow-clear
style="width: 130px"
>
<a-option :value="1">支持</a-option>
<a-option :value="0">不支持</a-option>
</a-select>
</a-form-item>
<a-form-item field="province_id" label="省份">
<a-select
v-model="queryForm.province_id"
placeholder="全部省份"
allow-clear
style="width: 150px"
@change="changeProvince"
@clear="clearProvince"
>
<a-option
v-for="item in provinceData"
:key="item.area_id"
:value="Number(item.area_id)"
:label="item.area_name"
>
{{ item.area_name }}
</a-option>
</a-select>
</a-form-item>
<a-form-item field="city_id" label="城市">
<a-select
v-model="queryForm.city_id"
placeholder="全部城市"
allow-clear
style="width: 150px"
:disabled="!queryForm.province_id"
@change="changeCity"
@clear="clearCity"
>
<a-option
v-for="item in cityData"
:key="item.area_id"
:value="Number(item.area_id)"
:label="item.area_name"
>
{{ item.area_name }}
</a-option>
</a-select>
</a-form-item>
<a-form-item field="county_id" label="区县">
<a-select
v-model="queryForm.county_id"
placeholder="全部区县"
allow-clear
style="width: 150px"
:disabled="!queryForm.city_id"
>
<a-option
v-for="item in countyData"
:key="item.area_id"
:value="Number(item.area_id)"
:label="item.area_name"
>
{{ item.area_name }}
</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleQuery">
<template #icon><icon-search /></template>
搜索
</a-button>
<a-button @click="handleResetQuery">
<template #icon><icon-loop /></template>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider style="margin: 12px 0 16px" />
<!-- 操作工具栏 -->
<div class="table-action-bar">
<a-space>
<a-button
v-if="hasPermission('admin:sysPharmacy:add')"
type="primary"
@click="handleAdd"
>
<template #icon><icon-plus /></template>
新增药房
</a-button>
</a-space>
</div>
<!-- 数据表格 -->
<a-table
:columns="columns"
:data="tableData"
:loading="tableLoading"
:pagination="paginationProps"
row-key="pharmacy_id"
:scroll="{ x: 1600 }"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<!-- 序号列 -->
<template #index="{ rowIndex }">
{{ (pager.page - 1) * pager.page_size + rowIndex + 1 }}
</template>
<!-- 基础邮费 -->
<template #postage="{ record }">
<span class="price-text">¥{{ Number(record.postage || 0).toFixed(2) }}</span>
</template>
<!-- 包邮门槛 -->
<template #free_shipping_threshold="{ 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>
<!-- 是否支持自提 -->
<template #is_pickup="{ 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>
<!-- 详细地址(带 Tooltip) -->
<template #full_address="{ record }">
<a-tooltip :content="record.full_address || record.address || '-'">
<div class="ellipsis-text">
{{ record.full_address || record.address || '-' }}
</div>
</a-tooltip>
</template>
<!-- 状态快捷开关(有权限可操作切换,无权限展示只读标签) -->
<template #status="{ record }">
<a-switch
v-if="hasPermission('admin:sysPharmacy:status')"
:model-value="record.status === 1"
:loading="record._statusLoading"
type="round"
size="small"
@change="(val) => handleStatusToggle(record, val)"
>
<template #checked>正常</template>
<template #unchecked>禁用</template>
</a-switch>
<a-tag v-else :color="record.status === 1 ? 'green' : 'red'" size="small">
{{ record.status === 1 ? '正常' : '禁用' }}
</a-tag>
</template>
<!-- 操作列 -->
<template #action="{ record }">
<a-space :size="0">
<a-button
v-if="hasPermission('admin:sysPharmacy:detail')"
type="text"
size="small"
@click="handleDetail(record)"
>
<template #icon><icon-eye /></template>
详情
</a-button>
<a-button
v-if="hasPermission('admin:sysPharmacy:edit')"
type="text"
size="small"
@click="handleEdit(record)"
>
<template #icon><icon-edit /></template>
修改
</a-button>
<a-button
v-if="hasPermission('admin:sysPharmacy:doctor')"
type="text"
size="small"
@click="handleOpenDoctorDrawer(record)"
>
<template #icon><icon-user-group /></template>
关联医生列表
</a-button>
<!-- <a-button
v-if="hasPermission('admin:sysPharmacy:remove')"
type="text"
status="danger"
size="small"
@click="handleDeleteClick(record)"
>
<template #icon><icon-delete /></template>
删除
</a-button> -->
</a-space>
</template>
</a-table>
<!-- 新增 / 编辑 / 详情 弹窗 -->
<PharmacyModal ref="modalRef" @success="handleQuery" />
<!-- 关联医生列表抽屉 -->
<PharmacyDoctorDrawer ref="doctorDrawerRef" />
<!-- 医生分流迁移抽屉 -->
<PharmacyTransferDrawer ref="transferDrawerRef" @success="handleQuery" />
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed } from 'vue';
import { Message, Modal } from '@arco-design/web-vue';
import { usePermissionStore } from '@/store/permission';
import {
getPharmacyPage,
updatePharmacyStatus,
deletePharmacy,
getAffectedDoctors,
} from '@/api/basic/pharmacy';
import { getAdminAreaList } from '@/api/basic/list';
import PharmacyModal from './components/pharmacyModal.vue';
import PharmacyDoctorDrawer from './components/pharmacyDoctorDrawer.vue';
import PharmacyTransferDrawer from './components/pharmacyTransferDrawer.vue';
const permissionStore = usePermissionStore();
// 统一权限检查方法(支持超级管理员 * 和指定权限标识)
const hasPermission = (permission) => {
const perms = permissionStore.buttonPermissions || [];
return perms.includes('*') || perms.includes(permission);
};
const queryFormRef = ref(null);
const modalRef = ref(null);
const doctorDrawerRef = ref(null);
const transferDrawerRef = ref(null);
const tableLoading = ref(false);
const tableData = ref([]);
// 区域数据
const provinceData = ref([]);
const cityData = ref([]);
const countyData = ref([]);
// 分页信息
const pager = reactive({
page: 1,
page_size: 10,
total: 0,
});
const paginationProps = computed(() => ({
total: pager.total,
current: pager.page,
pageSize: pager.page_size,
showTotal: true,
showJumper: true,
showPageSize: true,
pageSizeOptions: [10, 20, 50, 100],
}));
// 查询表单
const queryForm = reactive({
pharmacy_name: '',
pharmacy_code: '',
status: undefined,
is_pickup: undefined,
province_id: undefined,
city_id: undefined,
county_id: undefined,
});
// 表格列配置
const columns = [
{ title: '#', slotName: 'index', width: 60, align: 'center' },
{ title: '药房代码', dataIndex: 'pharmacy_code', width: 140 },
{ title: '药房名称', dataIndex: 'pharmacy_name', width: 200, ellipsis: true },
{ title: '基础邮费', slotName: 'postage', width: 120, align: 'right' },
{ title: '满额包邮门槛', slotName: 'free_shipping_threshold', width: 140, align: 'right' },
{ title: '自提', slotName: 'is_pickup', width: 90, align: 'center' },
{ title: '联系电话', dataIndex: 'telephone', width: 140 },
{ title: '地址', slotName: 'full_address', minWidth: 240 },
{ title: '状态', slotName: 'status', width: 100, align: 'center' },
{ title: '创建时间', dataIndex: 'created_at', width: 180 },
{ title: '操作', slotName: 'action', width: 350, fixed: 'right', align: 'center' },
];
// 获取区域信息
const fetchAreaList = async (parentId = '', areaType = 2) => {
try {
const res = await getAdminAreaList({
parent_id: parentId,
area_type: areaType,
});
if (res.code === 200) {
if (areaType === 2) provinceData.value = res.data || [];
if (areaType === 3) cityData.value = res.data || [];
if (areaType === 4) countyData.value = res.data || [];
}
} catch (error) {
console.error('获取区域失败:', error);
}
};
const changeProvince = (val) => {
queryForm.city_id = undefined;
queryForm.county_id = undefined;
cityData.value = [];
countyData.value = [];
if (val) {
fetchAreaList(val, 3);
}
};
const clearProvince = () => {
queryForm.province_id = undefined;
queryForm.city_id = undefined;
queryForm.county_id = undefined;
cityData.value = [];
countyData.value = [];
};
const changeCity = (val) => {
queryForm.county_id = undefined;
countyData.value = [];
if (val) {
fetchAreaList(val, 4);
}
};
const clearCity = () => {
queryForm.city_id = undefined;
queryForm.county_id = undefined;
countyData.value = [];
};
// 获取表格数据
const fetchTableData = async () => {
tableLoading.value = true;
try {
const params = {
page: pager.page,
page_size: pager.page_size,
};
if (queryForm.pharmacy_name) params.pharmacy_name = queryForm.pharmacy_name;
if (queryForm.pharmacy_code) params.pharmacy_code = queryForm.pharmacy_code;
if (queryForm.status !== undefined && queryForm.status !== null) params.status = queryForm.status;
if (queryForm.is_pickup !== undefined && queryForm.is_pickup !== null) params.is_pickup = queryForm.is_pickup;
if (queryForm.province_id) params.province_id = queryForm.province_id;
if (queryForm.city_id) params.city_id = queryForm.city_id;
if (queryForm.county_id) params.county_id = queryForm.county_id;
const res = await getPharmacyPage(params);
if (res.code === 200 && res.data) {
tableData.value = (res.data.data || []).map((item) => ({
...item,
_statusLoading: false,
}));
pager.total = res.data.total || 0;
pager.page = res.data.page || 1;
pager.page_size = res.data.page_size || 10;
} else {
Message.error(res.message || '获取药房列表失败');
}
} catch (err) {
console.error('获取药房列表异常:', err);
} finally {
tableLoading.value = false;
}
};
// 搜索
const handleQuery = () => {
pager.page = 1;
fetchTableData();
};
// 重置
const handleResetQuery = () => {
if (queryFormRef.value) {
queryFormRef.value.resetFields();
}
queryForm.pharmacy_name = '';
queryForm.pharmacy_code = '';
queryForm.status = undefined;
queryForm.is_pickup = undefined;
queryForm.province_id = undefined;
queryForm.city_id = undefined;
queryForm.county_id = undefined;
cityData.value = [];
countyData.value = [];
pager.page = 1;
fetchTableData();
};
// 分页变化
const handlePageChange = (page) => {
pager.page = page;
fetchTableData();
};
const handlePageSizeChange = (pageSize) => {
pager.page_size = pageSize;
pager.page = 1;
fetchTableData();
};
// 新增
const handleAdd = () => {
modalRef.value?.open('add');
};
// 详情
const handleDetail = (record) => {
modalRef.value?.open('detail', record.pharmacy_id);
};
// 编辑
const handleEdit = (record) => {
modalRef.value?.open('edit', record.pharmacy_id);
};
// 关联医生
const handleOpenDoctorDrawer = (record) => {
doctorDrawerRef.value?.open(record);
};
// 状态切换(启用 / 禁用)
const handleStatusToggle = async (record, val) => {
const newStatus = val ? 1 : 0;
const originalStatus = record.status;
// 场景 A:若是"启用"操作,无需迁移,直接调用原有接口
if (newStatus === 1) {
record._statusLoading = true;
try {
const res = await updatePharmacyStatus(record.pharmacy_id, 1);
if (res.code === 200) {
record.status = 1;
Message.success('已启用该药房');
} else {
record.status = originalStatus;
Message.error(res.message || '启用药房失败');
}
} catch (e) {
record.status = originalStatus;
Message.error('切换状态异常');
} finally {
record._statusLoading = false;
}
return;
}
// 场景 B:若是"禁用"操作,进行前置受影响医生预检
record._statusLoading = true;
try {
const preCheckRes = await getAffectedDoctors(record.pharmacy_id);
if (preCheckRes.code === 200 && preCheckRes.data?.default_doctor_count > 0) {
// 存在受影响默认医生:阻止直接禁用,回滚开关状态并唤起分流抽屉
record.status = originalStatus;
record._statusLoading = false;
transferDrawerRef.value?.open({
pharmacy: record,
action: 'disable',
affectedData: preCheckRes.data,
});
return;
}
// 无受影响医生:执行常规禁用
const res = await updatePharmacyStatus(record.pharmacy_id, 0);
if (res.code === 200) {
record.status = 0;
Message.success('已禁用该药房');
} else {
record.status = originalStatus;
Message.error(res.message || '禁用药房失败');
}
} catch (err) {
record.status = originalStatus;
Message.error('禁用药房预检或处理异常');
} finally {
record._statusLoading = false;
}
};
// 点击删除按钮逻辑(带预检)
const handleDeleteClick = async (record) => {
try {
// 1. 调用预检接口判断受影响医生
const preCheckRes = await getAffectedDoctors(record.pharmacy_id);
if (preCheckRes.code === 200 && preCheckRes.data?.default_doctor_count > 0) {
// 存在受影响医生:打开分流抽屉,动作指定为 'delete'
transferDrawerRef.value?.open({
pharmacy: record,
action: 'delete',
affectedData: preCheckRes.data,
});
return;
}
// 2. 无受影响医生:走常规删除二次弹窗确认
Modal.warning({
title: '确认删除药房',
content: `确定要删除药房【${record.pharmacy_name || '-'}】吗?删除后不可恢复!`,
hideCancel: false,
okButtonProps: { status: 'danger' },
onOk: async () => {
await executeDeletePharmacy(record.pharmacy_id);
},
});
} catch (error) {
console.error('预检受影响医生异常:', error);
Message.error('预检受影响医生异常');
}
};
// 执行常规删除请求
const executeDeletePharmacy = async (pharmacyId) => {
try {
const res = await deletePharmacy(pharmacyId);
if (res.code === 200) {
Message.success('药房删除成功');
// 如果当前页只有一条且非第一页,删除后向前退一页
if (tableData.value.length === 1 && pager.page > 1) {
pager.page -= 1;
}
fetchTableData();
} else {
Message.error(res.message || '删除药房失败');
}
} catch (err) {
Message.error('删除药房异常');
}
};
onMounted(() => {
fetchTableData();
fetchAreaList('', 2);
console.log('[药房管理] 当前登录用户拥有的按钮权限列表:', permissionStore.buttonPermissions);
});
</script>
<style lang="scss" scoped>
.app-container {
padding: 16px;
background-color: #fff;
border-radius: 4px;
}
.search-form {
:deep(.arco-form-item) {
margin-bottom: 12px;
}
}
.table-action-bar {
margin-bottom: 16px;
}
.price-text {
font-weight: 500;
color: #ff5722;
}
.ellipsis-text {
max-width: 320px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
@@ -0,0 +1,456 @@
<template>
<a-drawer
v-model:visible="visible"
:title="drawerTitle"
:width="960"
: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();
};
// 打开新增绑定弹窗
const openAddModal = () => {
if (allPharmacies.value.length === 0) {
fetchAllPharmacies();
}
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({
doctor_id: String(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(
record.doctor_pharmacy_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(
record.doctor_pharmacy_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>
+90 -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,25 @@ 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 () => {
if (pharmacyOptions.value.length > 0) return;
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 +1167,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: 280 },
];
// Table Data
@@ -1115,6 +1184,7 @@ const changeSelect = (value) => {
// 新增Satus
const handleAdd = () => {
loadPharmacyOptions();
modalVisible.value = true;
modalTitle.value = '新增医生';
modalSatus.value = 'add';
@@ -1135,6 +1205,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,10 +1270,18 @@ 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 = [];
}
}
};
// 修改
const handleUpdate = async (record) => {
loadPharmacyOptions();
modalVisible.value = true;
modalTitle.value = '修改医生';
modalSatus.value = 'edit';
@@ -1266,6 +1346,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 +1473,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;
@@ -1784,7 +1872,6 @@ onMounted(() => {
handelAreaList("", "", 2);
handleBankList();
});
</script>
+43 -4
View File
@@ -5,8 +5,18 @@
<a-form-item field="product_name" label="药品名称">
<a-input :style="{ width: '182px' }" v-model="queryForm.product_name" placeholder="请输入药品名称或通用名" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="product_pharmacy_code" label="药店编码">
<a-input :style="{ width: '182px' }" v-model="queryForm.product_pharmacy_code" placeholder="请输入药品编码" @press-enter="handleQuery" />
<a-form-item field="pharmacy_id" label="所属药房">
<a-select v-model="queryForm.pharmacy_id" placeholder="请选择所属药房" allow-clear allow-search :style="{ width: '182px' }">
<a-option v-for="item in pharmacyList" :key="item.pharmacy_id" :value="String(item.pharmacy_id)">
{{ item.pharmacy_name }}
</a-option>
</a-select>
</a-form-item>
<a-form-item field="product_pharmacy_code" label="第三方药品编码">
<a-input :style="{ width: '182px' }" v-model="queryForm.product_pharmacy_code" placeholder="请输入第三方药品编码" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="pharmacy_code" label="药店编码">
<a-input :style="{ width: '182px' }" v-model="queryForm.pharmacy_code" placeholder="请输入药店编码" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="manufacturer" label="生产企业">
<a-input :style="{ width: '182px' }" v-model="queryForm.manufacturer" placeholder="请输入生产企业" @press-enter="handleQuery" />
@@ -47,6 +57,7 @@
<!-- table -->
<a-table :columns="columns" :data="tableData"
:scroll="{ x: 1700 }"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
:pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.total, current: currentPage }"
row-key="family_id" @selection-change="(selection) => {deleteData = selection;console.log(selection)}"
@@ -54,6 +65,12 @@
<template #doctor_id="{record,rowIndex}">
<div>{{(rowIndex+1)+(pager.page-1)*pager.page_size}}</div>
</template>
<template #pharmacy_name="{record}">
<div>{{ record.pharmacy_name || record.pharmacy?.pharmacy_name || '-' }}</div>
<div v-if="record.pharmacy_code || record.pharmacy?.pharmacy_code" style="font-size: 12px; color: #86909c;">
{{ record.pharmacy_code || record.pharmacy?.pharmacy_code }}
</div>
</template>
<template #action="{ record }">
<a-space>
<a-button v-has="'admin:platformMedinceList:detail'" type="text"
@@ -84,7 +101,9 @@
<script setup>
import { reactive, ref, getCurrentInstance, onMounted, nextTick, watch, computed } from 'vue';
import { getMedinceList,getMedinceDetail} from '@/api/medince/list';
import { getPharmacyList } from '@/api/basic/pharmacy';
import { downloadFile } from '@/utils/downloadFile';
const pharmacyList = ref([]);
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
@@ -111,7 +130,9 @@
};
// form
const queryForm = reactive({
pharmacy_id: undefined,
pharmacy_code: '',
product_pharmacy_code: '',
});
const modalForm = reactive({
user:{},
@@ -140,10 +161,12 @@
{ title: '编号', dataIndex: 'doctor_id', slotName: 'doctor_id', width: '90' },
{ title: '药品名称', dataIndex: 'product_name',width:200 },
{ title: '规格', dataIndex: 'product_spec',width:200 },
{ title: '所属药房', dataIndex: 'pharmacy_name', slotName: 'pharmacy_name', width: 170, ellipsis: true },
{ title: '单价(元)', dataIndex: 'product_price', slotName: 'product_price',width: 150 },
{ title: '批准文号', dataIndex: 'license_number',width:200 },
{ title: '生产厂家', dataIndex: 'manufacturer',width:200 },
{ title: '药店编码', dataIndex: 'product_pharmacy_code',width:100 },
{ title: '第三方药品编码', dataIndex: 'product_pharmacy_code', width: 120, ellipsis: true },
// { title: '药房编码', dataIndex: 'pharmacy_code', width: 160, ellipsis: true },
// { title: '启用状态', dataIndex: 'status', slotName: 'status' },
{ title: '操作', slotName: 'action', fixed: "right", width: 180 },
];
@@ -234,6 +257,9 @@
// 重置搜索
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
queryForm.pharmacy_id = undefined;
queryForm.pharmacy_code = '';
queryForm.product_pharmacy_code = '';
getMedinceInfo(queryForm);
}
const handlExport=async(type)=>{
@@ -274,8 +300,21 @@
proxy.$loading.hide();
}
// 获取药房下拉列表
const fetchPharmacyList = async () => {
try {
const res = await getPharmacyList();
if (res.code === 200) {
pharmacyList.value = res.data || [];
}
} catch (e) {
console.error('获取药房列表异常:', e);
}
};
onMounted(() => {
getMedinceInfo(pager);
fetchPharmacyList();
});
</script>
+44 -3
View File
@@ -12,8 +12,18 @@
<a-input :style="{ width: '182px' }" v-model="queryForm.mnemonic_code" placeholder="请输入药品助记码" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="product_pharmacy_code" label="药店编码">
<a-input :style="{ width: '182px' }" v-model="queryForm.product_pharmacy_code" placeholder="请输入药品编码" @press-enter="handleQuery" />
<a-form-item field="pharmacy_code" label="药店编码">
<a-input :style="{ width: '182px' }" v-model="queryForm.pharmacy_code" placeholder="请输入药品编码" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="pharmacy_id" label="所属药房">
<a-select v-model="queryForm.pharmacy_id" placeholder="请选择所属药房" allow-clear allow-search :style="{ width: '182px' }">
<a-option v-for="item in pharmacyList" :key="item.pharmacy_id" :value="item.pharmacy_id">
{{ item.pharmacy_name }}
</a-option>
</a-select>
</a-form-item>
<a-form-item field="pharmacy_code" label="药房编码">
<a-input :style="{ width: '182px' }" v-model="queryForm.pharmacy_code" placeholder="请输入药房编码" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="manufacturer" label="生产企业">
<a-input :style="{ width: '182px' }" v-model="queryForm.manufacturer" placeholder="请输入生产企业" @press-enter="handleQuery" />
@@ -69,6 +79,7 @@
<!-- table -->
<a-table :columns="columns" :data="tableData"
:scroll="{ x: 2000 }"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
:pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.total, current: currentPage }"
row-key="product_id" @selection-change="(selection) => {deleteData = selection;console.log(selection)}"
@@ -76,6 +87,12 @@
<template #doctor_id="{record,rowIndex}">
<div>{{(rowIndex+1)+(pager.page-1)*pager.page_size}}</div>
</template>
<template #pharmacy_name="{record}">
<div>{{ record.pharmacy_name || '-' }}</div>
<div v-if="record.pharmacy_code" style="font-size: 12px; color: #86909c;">
{{ record.pharmacy_code }}
</div>
</template>
<template #product_status="{record}">
<a-tag v-if="record.product_status == 1" color="green">{{formatProductStatus(record.product_status)}}</a-tag>
<a-tag v-else-if="record.product_status == 2" color="red">{{formatProductStatus(record.product_status)}}</a-tag>
@@ -113,8 +130,10 @@
<script setup>
import { reactive, ref, getCurrentInstance, onMounted, nextTick, watch, computed } from 'vue';
import { getSysMedinceList,getSysMedinceDetail,exportProduct} from '@/api/medince/list';
import { getPharmacyList } from '@/api/basic/pharmacy';
import { downloadFile } from '@/utils/downloadFile';
import {formatProductStatus} from '@/utils/format';
const pharmacyList = ref([]);
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
@@ -142,6 +161,8 @@
};
// form
const queryForm = reactive({
pharmacy_id: undefined,
pharmacy_code: '',
order:{
stock:''
}
@@ -174,10 +195,11 @@
{ title: '药品名称', dataIndex: 'product_name',width:200 },
{ title: '通用名称', dataIndex: 'common_name',width:200 },
{ title: '规格', dataIndex: 'product_spec',width:200 },
{ title: '所属药房', dataIndex: 'pharmacy_name', slotName: 'pharmacy_name', width: 170, ellipsis: true },
{ title: '单价(元)', dataIndex: 'product_price', slotName: 'product_price',width: 150 },
{ title: '批准文号', dataIndex: 'license_number',width:200 },
{ title: '生产厂家', dataIndex: 'manufacturer',width:200 },
{ title: '药店编码', dataIndex: 'product_pharmacy_code',width:100 },
{ title: '药店编码', dataIndex: 'pharmacy_code',width:100 },
{ title: '库存', dataIndex: 'stock',width:100 },
{ title: '购买上限', dataIndex: 'prescription_num',width:100 },
{ title: '状态', dataIndex: 'product_status',slotName:'product_status',width:100 },
@@ -196,6 +218,9 @@
modalTitle.value = '新增药品';
modalSatus.value = 'add';
modalForm.product_id ='';
modalForm.pharmacy_id = '';
modalForm.pharmacy_name = '';
modalForm.pharmacy_code = '';
modalForm.frequency_use =null;
modalForm.available_days =null;
modalForm.product_name ='';
@@ -296,6 +321,8 @@
// 重置搜索
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
queryForm.pharmacy_id = undefined;
queryForm.pharmacy_code = '';
queryForm.order.stock='';
getMedinceInfo(queryForm);
}
@@ -339,8 +366,22 @@
}
proxy.$loading.hide();
}
// 获取药房下拉数据
const fetchPharmacyList = async () => {
try {
const res = await getPharmacyList();
if (res.code === 200) {
pharmacyList.value = res.data || [];
}
} catch (e) {
console.error('获取药房列表异常:', e);
}
};
onMounted(() => {
getMedinceInfo(pager);
fetchPharmacyList();
});
</script>
+71 -3
View File
@@ -195,6 +195,24 @@
<a-option :value="2">上报失败</a-option>
</a-select>
</a-form-item>
<a-form-item field="pharmacy_id" label="发货药房">
<a-select
v-model="queryForm.pharmacy_id"
placeholder="全部药房"
allow-clear
allow-search
:style="{ width: '182px' }"
>
<a-option
v-for="item in pharmacyList"
:key="item.pharmacy_id"
:value="item.pharmacy_id"
:label="item.pharmacy_name"
>
{{ item.pharmacy_name }}
</a-option>
</a-select>
</a-form-item>
<a-row>
<a-form-item field="delivery_range_time" label="发货时间范围">
<a-range-picker
@@ -252,7 +270,7 @@
<!-- table -->
<a-table
:columns="columns"
:scroll="{ x: 1980 }"
:scroll="{ x: 2140 }"
:data="tableData"
:pagination="{
'show-total': true,
@@ -300,6 +318,9 @@
record.patient_sex == 1 ? '男,' : '女,'
}}{{ record.patient_age }}岁)
</template>
<template #pharmacy_name="{ record }">
<span>{{ record.pharmacy_name || '-' }}</span>
</template>
<template #action="{ record }">
<a-space>
<a-button
@@ -409,6 +430,37 @@
</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 label="药房名称:">
<div class="box">
<span>{{ modalForm.pharmacy_name || modalForm.pharmacy?.pharmacy_name || '-' }}</span>
</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="药房代码:">
<div class="box">{{ modalForm.pharmacy_code || modalForm.pharmacy?.pharmacy_code || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="联系电话:">
<div class="box">{{ modalForm.pharmacy?.telephone || '-' }}</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="药房地址:">
<div class="box">{{ modalForm.pharmacy?.full_address || modalForm.pharmacy?.address || '-' }}</div>
</a-form-item>
</a-col>
</a-row>
<a-divider />
<div class="titlebox" v-if="modalForm.order_product_refund">
<div class="bar"></div>
<div class="name">退款信息</div>
@@ -878,6 +930,7 @@ import {
inquiryCase,
exportProduct
} from '@/api/order/list';
import { getPharmacyList } from '@/api/basic/pharmacy';
import { parseTime } from '@/utils/parseTime';
import {
formatDoctorTitle,
@@ -888,6 +941,7 @@ import {
} from '@/utils/format';
const IMG_URL = import.meta.env.VITE_IMG_URL;
import { downloadFile } from '@/utils/downloadFile';
const pharmacyList = ref([]);
// Akiraka 20230210 删除数据
const deleteData = ref([]);
// Akiraka 20230210 删除对话框
@@ -1044,6 +1098,7 @@ const columns = [
{ title: '药品名称', dataIndex: 'product_name', width: 150 },
{ title: '药品备注', dataIndex: 'remarks', width: 130 },
{ title: '就诊人联系电话', dataIndex: 'patient_mobile', width: 130 },
{ title: '发货药房', dataIndex: 'pharmacy_name', slotName: 'pharmacy_name', width: 160, ellipsis: true },
{ title: '订单金额', dataIndex: 'amount_total', slotName: 'amount_total' },
{
title: '实付金额',
@@ -1245,8 +1300,8 @@ const closeChangeOk = (data) => {
// 重置搜索
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
queryForm.pharmacy_id = undefined;
currentPage.value=1;
//getProductInfo(queryForm);
handleQuery();
};
const openDcotor = () => {
@@ -1298,11 +1353,24 @@ const handlExport=async(type)=>{
}
proxy.$loading.hide();
}
// 获取药房下拉数据
const fetchPharmacyList = async () => {
try {
const res = await getPharmacyList();
if (res.code === 200) {
pharmacyList.value = res.data || [];
}
} catch (e) {
console.error('获取药房列表异常:', e);
}
};
onMounted(() => {
let userInfo=localStorage.getItem('manage-userInfo')?JSON.parse(localStorage.getItem('manage-userInfo')):{};
showTransferDoctor.value=userInfo.role_name!='处方流转平台';
getProductInfo(pager);
fetchPharmacyList();
});
</script>
@@ -44,6 +44,24 @@
<a-option :value="2">审核驳回</a-option>
</a-select>
</a-form-item>
<a-form-item field="pharmacy_id" label="开方药房">
<a-select
v-model="queryForm.pharmacy_id"
placeholder="全部药房"
allow-clear
allow-search
:style="{ width: '182px' }"
>
<a-option
v-for="item in pharmacyList"
:key="item.pharmacy_id"
:value="item.pharmacy_id"
:label="item.pharmacy_name"
>
{{ item.pharmacy_name }}
</a-option>
</a-select>
</a-form-item>
<a-row>
<a-form-item field="pharmacist_verify_range_time" label="审核时间范围">
<a-range-picker
@@ -86,7 +104,7 @@
<!-- table -->
<a-table :columns="columns" :data="tableData"
:scroll="{ x: 1400 }"
:scroll="{ x: 1560 }"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
:pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.total, current: currentPage }"
row-key="order_prescription_id" @selection-change="(selection) => {deleteData = selection;console.log(selection)}"
@@ -113,6 +131,9 @@
record.patient_sex == 1 ? '男,' : '女,'
}}{{ record.patient_age }}岁)</div>
</template>
<template #pharmacy_name="{record}">
<span>{{ record.pharmacy_name || '-' }}</span>
</template>
<template #order_prescription_icd="{record}">
<div class="doctor_name" :title="record.order_prescription_icd">{{record.order_prescription_icd}}</div>
</template>
@@ -146,9 +167,12 @@
<script setup>
import { reactive, ref, getCurrentInstance, onMounted, nextTick, watch, computed } from 'vue';
import { getPrescriptionList,getPrescriptionDetail,exportPrescription} from '@/api/prescription/list';
import { getPharmacyList } from '@/api/basic/pharmacy';
import {formatPrescriptionStatus} from "@/utils/format"
import { downloadFile } from '@/utils/downloadFile';
const pharmacyList = ref([]);
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
@@ -245,6 +269,7 @@ watch(() => queryForm.expired_range_time,
{ title: '药师姓名', dataIndex: 'pharmacist_name',slotName:'pharmacist_name' },
{ title: '就诊人', dataIndex: 'patient_name', slotName: 'patient_name',width:180 },
{ title: '就诊人联系电话', dataIndex: 'mobile',width:130 },
{ title: '开方药房', dataIndex: 'pharmacy_name', slotName: 'pharmacy_name', width: 160, ellipsis: true },
{ title: '诊断', dataIndex: 'order_prescription_icd',slotName: 'order_prescription_icd' },
{ title: '处方状态', dataIndex: 'prescription_status',slotName:'prescription_status' },
{ title: '开方时间', dataIndex: 'created_at',slotName:'created_at',width:180 },
@@ -338,10 +363,9 @@ watch(() => queryForm.expired_range_time,
// 重置搜索
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
//getPrescriptionInfo(queryForm);
queryForm.pharmacy_id = undefined;
currentPage.value=1;
//getProductInfo(queryForm);
handleQuery();
handleQuery();
}
const handlExport=async(type)=>{
proxy.$loading.show();
@@ -381,9 +405,21 @@ watch(() => queryForm.expired_range_time,
proxy.$loading.hide();
}
// 获取药房下拉数据
const fetchPharmacyList = async () => {
try {
const res = await getPharmacyList();
if (res.code === 200) {
pharmacyList.value = res.data || [];
}
} catch (e) {
console.error('获取药房列表异常:', e);
}
};
onMounted(() => {
getPrescriptionInfo(pager);
fetchPharmacyList();
});
</script>
@@ -44,6 +44,24 @@
<a-option :value="2">审核驳回</a-option>
</a-select>
</a-form-item>
<a-form-item field="pharmacy_id" label="开方药房">
<a-select
v-model="queryForm.pharmacy_id"
placeholder="全部药房"
allow-clear
allow-search
:style="{ width: '182px' }"
>
<a-option
v-for="item in pharmacyList"
:key="item.pharmacy_id"
:value="item.pharmacy_id"
:label="item.pharmacy_name"
>
{{ item.pharmacy_name }}
</a-option>
</a-select>
</a-form-item>
<a-row>
<a-form-item field="pharmacist_verify_range_time" label="审核时间范围">
<a-range-picker
@@ -86,7 +104,7 @@
<!-- table -->
<a-table :columns="columns" :data="tableData"
:scroll="{ x: 1400 }"
:scroll="{ x: 1560 }"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
:pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.total, current: currentPage }"
row-key="order_prescription_id" @selection-change="(selection) => {deleteData = selection;console.log(selection)}"
@@ -113,6 +131,9 @@
record.patient_sex == 1 ? '男,' : '女,'
}}{{ record.patient_age }}岁)</div>
</template>
<template #pharmacy_name="{record}">
<span>{{ record.pharmacy_name || '-' }}</span>
</template>
<template #order_prescription_icd="{record}">
<div class="doctor_name" :title="record.order_prescription_icd">{{record.order_prescription_icd}}</div>
</template>
@@ -146,10 +167,12 @@
<script setup>
import { reactive, ref, getCurrentInstance, onMounted, nextTick, watch, computed } from 'vue';
import { getPrescriptionList,getPrescriptionDetail,exportPrescription} from '@/api/prescription/transfer-list';
import { getPharmacyList } from '@/api/basic/pharmacy';
import {formatPrescriptionStatus} from "@/utils/format"
import { downloadFile } from '@/utils/downloadFile';
const showTransferDoctor = ref(false)
const pharmacyList = ref([]);
// Akiraka 20230210 删除数据
const deleteData = ref([])
@@ -247,6 +270,7 @@ watch(() => queryForm.expired_range_time,
{ title: '药师姓名', dataIndex: 'pharmacist_name',slotName:'pharmacist_name' },
{ title: '就诊人', dataIndex: 'patient_name', slotName: 'patient_name',width:180 },
{ title: '就诊人联系电话', dataIndex: 'mobile',width:130 },
{ title: '开方药房', dataIndex: 'pharmacy_name', slotName: 'pharmacy_name', width: 160, ellipsis: true },
{ title: '诊断', dataIndex: 'order_prescription_icd',slotName: 'order_prescription_icd' },
{ title: '处方状态', dataIndex: 'prescription_status',slotName:'prescription_status' },
{ title: '开方时间', dataIndex: 'created_at',slotName:'created_at',width:180 },
@@ -340,10 +364,9 @@ watch(() => queryForm.expired_range_time,
// 重置搜索
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
//getPrescriptionInfo(queryForm);
queryForm.pharmacy_id = undefined;
currentPage.value=1;
//getProductInfo(queryForm);
handleQuery();
handleQuery();
}
const handlExport=async(type)=>{
proxy.$loading.show();
@@ -383,11 +406,23 @@ watch(() => queryForm.expired_range_time,
proxy.$loading.hide();
}
// 获取药房下拉数据
const fetchPharmacyList = async () => {
try {
const res = await getPharmacyList();
if (res.code === 200) {
pharmacyList.value = res.data || [];
}
} catch (e) {
console.error('获取药房列表异常:', e);
}
};
onMounted(() => {
let userInfo=localStorage.getItem('manage-userInfo')?JSON.parse(localStorage.getItem('manage-userInfo')):{};
showTransferDoctor.value=userInfo.role_name!='处方流转平台';
getPrescriptionInfo(pager);
fetchPharmacyList();
});
</script>