feat: 优化了若干接口页面,可用性存疑、科室管理、处方发送成功页面
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
FileTextOutlined,
|
||||
HistoryOutlined,
|
||||
MedicineBoxOutlined,
|
||||
CheckCircleFilled,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
PlusOutlined,
|
||||
@@ -790,6 +791,13 @@ const setVisible = (value, instruction = ''): void => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处方发送成功页数据
|
||||
* 发送成功后右区切到成功面板(tabType=3)展示本单摘要,
|
||||
* 「代付收款」直接用这里的 order_id 拉代付码,医生不用再去处方记录抽屉里找
|
||||
*/
|
||||
const sentPrescriptionInfo = ref<any>(null);
|
||||
|
||||
/**
|
||||
* 选择患者:已接诊直接进开方工作台;未接诊进轻量接诊操作区(不再进旧患者信息大页)
|
||||
*/
|
||||
@@ -814,6 +822,10 @@ const selectPatient = async (patient: Patient, isUpdateTabType = true) => {
|
||||
if (patient.status !== 2 && tabType.value === 2) {
|
||||
tabType.value = 1;
|
||||
}
|
||||
// 发送成功页数据不跨刷新:缓存里是成功页但数据已丢时,按接诊状态回落
|
||||
if (tabType.value === 3 && !sentPrescriptionInfo.value) {
|
||||
tabType.value = patient.status === 2 ? 2 : 1;
|
||||
}
|
||||
}
|
||||
activePatient.value = patient.user_patient;
|
||||
|
||||
@@ -1207,6 +1219,14 @@ const sendPrescription = () => {
|
||||
special_prescription_id: appliedSpecialPrescriptionId.value || 0,
|
||||
}).then(async (res) => {
|
||||
message.success('处方已发送');
|
||||
// 先记录本次发送结果(下面会清空诊断/购物车,成功页要用的数据在这里先落住)
|
||||
sentPrescriptionInfo.value = {
|
||||
order_id: Number(res?.order_id || 0),
|
||||
order_no: res?.order_no || '',
|
||||
total_pay_price: res?.total_pay_price || '',
|
||||
patient_name: activePatient.value?.name || '',
|
||||
diagnosis: diagnosis.value,
|
||||
};
|
||||
// 有 VIP 时同步保存病历(诊断/医嘱已与处方同源)
|
||||
if (canUseMedicalRecord.value) {
|
||||
try {
|
||||
@@ -1249,8 +1269,9 @@ const sendPrescription = () => {
|
||||
// 清空当前数据
|
||||
clearPrescriptionCart(false);
|
||||
clearReceptionDraft(getRegisterId());
|
||||
// 发送成功后留在开方工作台,刷新处方列表即可
|
||||
tabType.value = 2;
|
||||
// 转诊单要等中医诊所确认,留在开方工作台展示转方卡;
|
||||
// 普通发送进入成功页(tabType=3):摘要 + 代付入口,不自动打开处方记录抽屉
|
||||
tabType.value = needTransfer && transferPrescriptionId ? 2 : 3;
|
||||
salespersonTransferPrescriptionId.value = 0;
|
||||
checkSalespersonTransfer();
|
||||
|
||||
@@ -4317,6 +4338,69 @@ function onStoreSelectOpenChange(open: boolean) {
|
||||
<!-- /rx-workspace -->
|
||||
</Spin>
|
||||
</Page>
|
||||
<!-- 处方发送成功页:摘要与代付入口都在面板上,不自动打开处方记录抽屉 -->
|
||||
<Page v-else-if="tabType === 3" class="prescription-panel">
|
||||
<PatientBriefBar
|
||||
:register="patientInfo"
|
||||
:patient="activePatient"
|
||||
:category="category"
|
||||
:store-name="currentStoreName"
|
||||
:is-online="false"
|
||||
/>
|
||||
<!-- 成功页布局对齐 C 端付款成功页(shop/paied.vue):
|
||||
白色圆角卡居中大图标 + 胶囊小按钮,明细行复用 C 端收银台(record-pay.vue)的 label/value 形态 -->
|
||||
<div class="rx-sent-wrap">
|
||||
<!-- 卡 1:成功卡(大图标 + 标题 + 描边胶囊小按钮) -->
|
||||
<div class="rx-sent-card rx-sent-hero">
|
||||
<CheckCircleFilled class="rx-sent-icon" />
|
||||
<h3 class="rx-sent-title">处方已发送</h3>
|
||||
<p class="rx-sent-sub">已推送到患者微信 · 支付完成后门店按方发药</p>
|
||||
<div class="rx-sent-pills">
|
||||
<Button shape="round" size="small" @click="openPatientRxHistoryDrawer">处方记录</Button>
|
||||
<Button shape="round" size="small" danger @click="endOfDiagnosis">结束诊断</Button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 卡 2:订单明细(label 加粗深色 / 值灰色,合计行大号金额) -->
|
||||
<div class="rx-sent-card rx-sent-detail">
|
||||
<div class="rx-sent-row">
|
||||
<span>订单号</span>
|
||||
<b>{{ sentPrescriptionInfo?.order_no || '—' }}</b>
|
||||
</div>
|
||||
<div class="rx-sent-row">
|
||||
<span>患者</span>
|
||||
<b>{{ sentPrescriptionInfo?.patient_name || '—' }}</b>
|
||||
</div>
|
||||
<div v-if="sentPrescriptionInfo?.diagnosis" class="rx-sent-row">
|
||||
<span>诊断</span>
|
||||
<b>{{ sentPrescriptionInfo.diagnosis }}</b>
|
||||
</div>
|
||||
<div class="rx-sent-row">
|
||||
<span>订单状态</span>
|
||||
<Tag color="orange" class="rx-sent-tag">待患者支付</Tag>
|
||||
</div>
|
||||
<div class="rx-sent-total">
|
||||
<span>总金额</span>
|
||||
<span class="rx-sent-total-val">合计: <b>¥{{ sentPrescriptionInfo?.total_pay_price || '0.00' }}</b></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部操作:胶囊主按钮(代付收款)+ 描边胶囊次按钮(继续开方),对齐 C 端底部通栏确认钮 -->
|
||||
<div class="rx-sent-ops">
|
||||
<!-- 代付入口:患者不便操作手机时,医生代其发起收款(复用代付码弹窗) -->
|
||||
<Button
|
||||
v-if="sentPrescriptionInfo?.order_id"
|
||||
type="primary"
|
||||
shape="round"
|
||||
size="large"
|
||||
class="rx-sent-main"
|
||||
@click="openProxyPayQrcode(Number(sentPrescriptionInfo.order_id))"
|
||||
>
|
||||
<QrcodeOutlined />
|
||||
代付收款
|
||||
</Button>
|
||||
<Button shape="round" size="large" class="rx-sent-next" @click="tabType = 2">继续开方</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
<div v-else-if="tabType === 0" class="prescription-panel">
|
||||
<Empty/>
|
||||
</div>
|
||||
@@ -4604,6 +4688,10 @@ function onStoreSelectOpenChange(open: boolean) {
|
||||
|
||||
.prescription-panel {
|
||||
padding: 1rem;
|
||||
/* 轻量接诊面板(待接诊/空态)在锁高布局里自身滚动:
|
||||
健康信息、特殊处方导入卡、转方卡叠加后内容会超一屏,父级 overflow:hidden 会把超出部分裁掉 */
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
/* 开方工作台:占满右侧主区,内部再拆「固定顶栏 / 可滚动内容」 */
|
||||
.prescription-panel--rx {
|
||||
@@ -4616,8 +4704,129 @@ function onStoreSelectOpenChange(open: boolean) {
|
||||
/* 内边距交给内部 content,避免双层 padding 挤掉高度 */
|
||||
padding: 0 !important;
|
||||
}
|
||||
/* Page 根下的内容区(无 header 时即第一个/唯一子节点) */
|
||||
.prescription-panel--rx :deep(> .h-full) {
|
||||
/* 处方发送成功页:视觉对齐 C 端付款成功页(浅灰蓝底 + 白色圆角卡 + 胶囊按钮),
|
||||
颜色走主题变量保证暗色可用,主色用 --primary(与 C 端品牌蓝 #4175EE 同族) */
|
||||
.rx-sent-wrap {
|
||||
max-width: 480px;
|
||||
margin: 20px auto 0;
|
||||
padding: 14px 14px 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
border-radius: 14px;
|
||||
/* 对应 C 端页面底色 #F8FAFC:用 muted 低透明度自动适配暗色 */
|
||||
background: hsl(var(--muted) / 0.45);
|
||||
}
|
||||
.rx-sent-card {
|
||||
border-radius: 10px;
|
||||
background: hsl(var(--card, var(--background)));
|
||||
border: 1px solid hsl(var(--border) / 0.55);
|
||||
box-shadow: 0 1px 6px rgb(15 23 42 / 4%);
|
||||
}
|
||||
/* 成功卡:C 端 paied.vue 头卡形态(大图标居中 + 加粗标题 + 描边胶囊小按钮) */
|
||||
.rx-sent-hero {
|
||||
padding: 28px 20px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.rx-sent-icon {
|
||||
font-size: 52px;
|
||||
color: hsl(var(--primary));
|
||||
animation: rx-sent-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
@keyframes rx-sent-pop {
|
||||
0% {
|
||||
transform: scale(0.4);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.rx-sent-title {
|
||||
margin: 14px 0 4px;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
.rx-sent-sub {
|
||||
margin: 0;
|
||||
font-size: 12.5px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
.rx-sent-pills {
|
||||
margin-top: 18px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
/* 明细卡:C 端 record-pay.vue 的 good_ 行形态(label 加粗深色 / 值常规灰色) */
|
||||
.rx-sent-detail {
|
||||
padding: 6px 18px 14px;
|
||||
}
|
||||
.rx-sent-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: 42px;
|
||||
}
|
||||
.rx-sent-row > span {
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
.rx-sent-row > b {
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
text-align: right;
|
||||
word-break: break-all;
|
||||
}
|
||||
.rx-sent-tag {
|
||||
margin-inline-end: 0;
|
||||
}
|
||||
/* 合计行:C 端「合计:」小字 + 大号金额 */
|
||||
.rx-sent-total {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: 12px;
|
||||
margin-top: 4px;
|
||||
border-top: 1px solid hsl(var(--border) / 0.55);
|
||||
}
|
||||
.rx-sent-total > span {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
.rx-sent-total-val {
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
.rx-sent-total-val b {
|
||||
margin-left: 4px;
|
||||
font-size: 19px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
/* 底部操作:主按钮胶囊撑满(对齐 C 端底部通栏「确认」钮),次按钮等宽排布 */
|
||||
.rx-sent-ops {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.rx-sent-main {
|
||||
flex: 1.6;
|
||||
}
|
||||
.rx-sent-next {
|
||||
flex: 1;
|
||||
}
|
||||
/* Page 根下的内容区:未开 autoContentHeight 时 class 是 flex-1(开了才是 h-full),
|
||||
两种都要命中,否则「固定顶栏 + 内容滚动」的高度链路断裂,长处方整页无法滚动 */
|
||||
.prescription-panel--rx :deep(> .h-full),
|
||||
.prescription-panel--rx :deep(> .flex-1) {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 100% !important;
|
||||
|
||||
@@ -327,6 +327,14 @@ export async function getDoctorMyDiseaseListApi() {
|
||||
export async function saveServicePriceApi(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}save-service-price`, data);
|
||||
}
|
||||
/**
|
||||
* 保存每日号源上限(yii_doctor_info.register_num,0 表示不放号)
|
||||
* xk-api POST /doctor/save-register-num
|
||||
* @param data { register_num: number }
|
||||
*/
|
||||
export async function saveRegisterNumApi(data: { register_num: number }) {
|
||||
return requestClient.post<any>(`${prefix}save-register-num`, data);
|
||||
}
|
||||
/**
|
||||
* 创建医嘱
|
||||
* @param content
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
getDoctorMyDiseaseListApi,
|
||||
getDoctorMyDoctorOrderApi,
|
||||
getDoctorMyInfoApi,
|
||||
saveRegisterNumApi,
|
||||
saveServicePriceApi,
|
||||
} from '#/views/doctor/settings/api';
|
||||
import type { CommonPrescriptionResponse } from '#/views/doctor/settings/api';
|
||||
@@ -121,6 +122,21 @@ const saveServicePrice = () => {
|
||||
// getMyInfo();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* 保存每日号源上限
|
||||
* register_num 在医生信息表(yii_doctor_info)上,与挂号开关/价格(yii_doctor_service)分表,
|
||||
* 所以单独走 save-register-num 接口;0 表示不放号(患者端显示约满)
|
||||
*/
|
||||
const saveRegisterNum = () => {
|
||||
saveRegisterNumApi({
|
||||
register_num: data.value.register_num,
|
||||
}).then(() => {
|
||||
notification.success({
|
||||
message: '保存成功',
|
||||
duration: 2,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getDoctorMyDoctorOrder = () => {
|
||||
getDoctorMyDoctorOrderApi().then((res) => {
|
||||
@@ -461,6 +477,22 @@ const handleViewSignature = () => {
|
||||
【{{ data.service.register_status === 1 ? '开启' : '关闭' }}】
|
||||
</div>
|
||||
|
||||
<!-- 每日号源上限:挂号防超卖的额度来源,0 表示不放号(患者端显示约满) -->
|
||||
<div class="mb-3 flex items-center gap-4">
|
||||
每日号源上限
|
||||
<InputNumber
|
||||
v-model:value="data.register_num"
|
||||
:min="0"
|
||||
:max="999"
|
||||
:precision="0"
|
||||
style="min-width: 100px"
|
||||
@keydown.enter="saveRegisterNum"
|
||||
>
|
||||
<template #addonAfter> 个/天 </template>
|
||||
</InputNumber>
|
||||
<Button type="primary" @click="saveRegisterNum">保存</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2"></div>
|
||||
|
||||
<div class="mt-4 space-y-1"></div>
|
||||
|
||||
44
apps/web-antd/src/views/system/department/api/index.ts
Normal file
44
apps/web-antd/src/views/system/department/api/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
const prefix = 'department/';
|
||||
|
||||
/**
|
||||
* 获取科室树形列表(平铺返回,前端 treeConfig transform 组树)
|
||||
* xk-api GET /department/list
|
||||
* @param data name 科室名称模糊搜索
|
||||
*/
|
||||
export async function getDepartmentList(data: any) {
|
||||
return requestClient.get<any>(`${prefix}list`, { params: data });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取科室下拉选项(选择父级科室用,返回 [{id, name, pid, level}])
|
||||
* xk-api GET /department/option
|
||||
*/
|
||||
export async function getDepartmentOption() {
|
||||
return requestClient.get<any>(`${prefix}option`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增科室(level 由后端按 pid 推导,最多两级)
|
||||
* xk-api POST /department/create
|
||||
*/
|
||||
export async function createDepartment(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}create`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑科室
|
||||
* xk-api POST /department/update
|
||||
*/
|
||||
export async function updateDepartment(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}update`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除科室(物理删除,后端校验子科室与医生绑定)
|
||||
* xk-api POST /department/delete
|
||||
*/
|
||||
export async function deleteDepartment(data: Record<string, any>) {
|
||||
return requestClient.post<any>(`${prefix}delete`, data);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<script lang="ts" setup>
|
||||
// 科室新增/编辑弹窗
|
||||
// 父级科室选项在打开弹窗时从接口拉取(仅一级科室可选),编辑时排除自己防止自挂
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
import { createDepartment, getDepartmentOption, updateDepartment } from '../api';
|
||||
import { modalFormProps } from '../config/form';
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const gridApi = ref();
|
||||
|
||||
const [Form, formApi] = useVbenForm(modalFormProps);
|
||||
|
||||
/**
|
||||
* 拉取父级科室选项并注入 pid 下拉
|
||||
* 只允许一级科室作为父级(后端强制两级封顶),编辑时排除自身
|
||||
*/
|
||||
const loadParentOptions = async (excludeId?: number) => {
|
||||
const list = await getDepartmentOption();
|
||||
const options = [
|
||||
{ label: '作为一级科室(无父级)', value: 0 },
|
||||
...(list || [])
|
||||
.filter((item: any) => item.level === 1 && item.id !== excludeId)
|
||||
.map((item: any) => ({ label: item.name, value: item.id })),
|
||||
];
|
||||
formApi.updateSchema([
|
||||
{
|
||||
fieldName: 'pid',
|
||||
componentProps: {
|
||||
options,
|
||||
},
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
draggable: true,
|
||||
onCancel() {
|
||||
modalApi.close();
|
||||
},
|
||||
onConfirm: async () => {
|
||||
formApi.validate().then(async (e: any) => {
|
||||
if (e.valid) {
|
||||
const values = await formApi.getValues();
|
||||
modalApi.lock();
|
||||
const submitApi = isUpdate.value ? updateDepartment : createDepartment;
|
||||
submitApi(values)
|
||||
.then(() => {
|
||||
message.success('保存成功');
|
||||
gridApi.value?.query();
|
||||
modalApi.close();
|
||||
})
|
||||
.finally(() => {
|
||||
modalApi.unlock();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
onOpenChange(isOpen: boolean) {
|
||||
gridApi.value = isOpen ? modalApi.getData()?.gridApi : null;
|
||||
if (isOpen) {
|
||||
const { values, update } = modalApi.getData<Record<string, any>>();
|
||||
isUpdate.value = !!update;
|
||||
// 先重置再回填,避免上一次打开的残留值
|
||||
formApi.resetForm();
|
||||
loadParentOptions(update ? values?.id : undefined);
|
||||
if (values) {
|
||||
formApi.setValues(values);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Modal :title="`${isUpdate === true ? '编辑' : '新增'}科室`" class="w-[520px]">
|
||||
<Form />
|
||||
</Modal>
|
||||
</template>
|
||||
81
apps/web-antd/src/views/system/department/config/form.ts
Normal file
81
apps/web-antd/src/views/system/department/config/form.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
|
||||
/**
|
||||
* 科室新增/编辑弹窗表单
|
||||
* pid 的选项来自接口(仅一级科室可作为父级),在 modal.vue 的 onOpenChange 里
|
||||
* 通过 formApi.updateSchema 动态注入,禁止在此写死
|
||||
*/
|
||||
export const modalFormProps: VbenFormProps = {
|
||||
wrapperClass: 'grid-cols-12',
|
||||
commonConfig: {
|
||||
formItemClass: 'col-span-12',
|
||||
// 所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
layout: 'horizontal',
|
||||
showDefaultActions: false,
|
||||
schema: [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
fieldName: 'id',
|
||||
label: 'ID',
|
||||
dependencies: {
|
||||
show: false,
|
||||
triggerFields: ['id'],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '请输入科室名称',
|
||||
maxlength: 50,
|
||||
},
|
||||
fieldName: 'name',
|
||||
label: '科室名称',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
placeholder: '请选择父级科室(不选则为一级科室)',
|
||||
options: [],
|
||||
showSearch: true,
|
||||
optionFilterProp: 'label',
|
||||
},
|
||||
defaultValue: 0,
|
||||
fieldName: 'pid',
|
||||
label: '父级科室',
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '拼音首拼(如:儿科=EK,可不填)',
|
||||
maxlength: 50,
|
||||
},
|
||||
fieldName: 'pinyin_simple',
|
||||
label: '拼音首拼',
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '否',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '是',
|
||||
value: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
defaultValue: 0,
|
||||
fieldName: 'feature',
|
||||
label: '特色专科',
|
||||
rules: 'required',
|
||||
},
|
||||
],
|
||||
};
|
||||
29
apps/web-antd/src/views/system/department/config/search.ts
Normal file
29
apps/web-antd/src/views/system/department/config/search.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
|
||||
/**
|
||||
* 科室列表顶部搜索表单(按名称模糊搜索,后端会自动补齐父链保证树可渲染)
|
||||
*/
|
||||
export const formOptions: VbenFormProps = {
|
||||
// 默认展开
|
||||
collapsed: false,
|
||||
schema: [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: '输入科室名称',
|
||||
},
|
||||
defaultValue: '',
|
||||
fieldName: 'name',
|
||||
label: '科室名称',
|
||||
},
|
||||
],
|
||||
// 控制表单是否显示折叠按钮
|
||||
showCollapseButton: false,
|
||||
submitButtonOptions: {
|
||||
content: '查询',
|
||||
},
|
||||
// 是否在字段值改变时提交表单
|
||||
submitOnChange: true,
|
||||
// 按下回车时是否提交表单
|
||||
submitOnEnter: false,
|
||||
};
|
||||
93
apps/web-antd/src/views/system/department/config/table.ts
Normal file
93
apps/web-antd/src/views/system/department/config/table.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { getDepartmentList } from '../api';
|
||||
|
||||
interface RowType {
|
||||
id: number;
|
||||
name: string;
|
||||
pinyin_simple: string;
|
||||
pid: number;
|
||||
level: number;
|
||||
feature: number;
|
||||
doctor_count: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 科室树形表格配置
|
||||
* 后端返回平铺数据(含 id/pid),treeConfig.transform 自动组树;
|
||||
* 树形结构下分页会截断父子关系,故关闭分页、一次性全量展示(科室量级很小)
|
||||
*/
|
||||
export const gridOptions: VxeGridProps<RowType> = {
|
||||
checkboxConfig: {
|
||||
highlight: true,
|
||||
labelField: '',
|
||||
},
|
||||
columnConfig: {
|
||||
useKey: true,
|
||||
},
|
||||
rowConfig: {
|
||||
useKey: true,
|
||||
},
|
||||
columns: [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
{ field: 'name', align: 'left', title: '科室名称', treeNode: true, minWidth: 220 },
|
||||
{ field: 'pinyin_simple', title: '拼音首拼', width: 120 },
|
||||
{
|
||||
field: 'level',
|
||||
title: '层级',
|
||||
width: 100,
|
||||
slots: { default: 'level' },
|
||||
},
|
||||
{
|
||||
field: 'feature',
|
||||
title: '特色专科',
|
||||
width: 110,
|
||||
slots: { default: 'feature' },
|
||||
},
|
||||
{ field: 'doctor_count', title: '绑定医生数', width: 110 },
|
||||
{ field: 'created_at', title: '创建时间', width: 180 },
|
||||
{ field: 'updated_at', title: '更新时间', width: 180 },
|
||||
{
|
||||
type: 'html',
|
||||
align: 'right',
|
||||
title: '操作',
|
||||
slots: { default: 'action' },
|
||||
width: 240,
|
||||
},
|
||||
],
|
||||
treeConfig: {
|
||||
parentField: 'pid',
|
||||
rowField: 'id',
|
||||
transform: true,
|
||||
expandAll: true,
|
||||
},
|
||||
keepSource: true,
|
||||
pagerConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
// 树形全量加载,不传分页参数
|
||||
query: async (_params, formValues) => {
|
||||
return await getDepartmentList({ ...formValues });
|
||||
},
|
||||
},
|
||||
},
|
||||
height: 'auto',
|
||||
border: false,
|
||||
toolbarConfig: {
|
||||
// 是否显示搜索表单控制按钮
|
||||
// @ts-ignore 正式环境时有完整的类型声明
|
||||
search: true,
|
||||
refresh: true, // 刷新
|
||||
print: false, // 打印
|
||||
export: false, // 导出
|
||||
zoom: true, // 最大化最小化
|
||||
slots: {
|
||||
buttons: 'toolbar-actions',
|
||||
},
|
||||
},
|
||||
showOverflow: false,
|
||||
};
|
||||
150
apps/web-antd/src/views/system/department/index.vue
Normal file
150
apps/web-antd/src/views/system/department/index.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<script lang="ts" setup>
|
||||
// 科室字典管理(yii_department,老库表不迁库,管理入口收敛到新后台)
|
||||
// 树形展示一级/二级科室,支持新增、编辑、添加子科室、删除(后端校验医生绑定)
|
||||
import type { VxeGridListeners } from '#/adapter/vxe-table';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { message, Tag } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
|
||||
import { deleteDepartment } from './api';
|
||||
import FormModalDemo from './components/modal.vue';
|
||||
import { formOptions } from './config/search';
|
||||
import { gridOptions } from './config/table';
|
||||
|
||||
defineOptions({ name: 'SystemDepartment' });
|
||||
|
||||
const hasTopTableDropDownActions = ref(false);
|
||||
|
||||
const gridEvents: VxeGridListeners<any> = {
|
||||
checkboxChange() {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
const records = gridApi.grid.getCheckboxRecords();
|
||||
hasTopTableDropDownActions.value = records.length > 0;
|
||||
},
|
||||
checkboxAll() {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
const records = gridApi.grid.getCheckboxRecords();
|
||||
hasTopTableDropDownActions.value = records.length > 0;
|
||||
},
|
||||
};
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
gridEvents,
|
||||
});
|
||||
|
||||
const [FormModal, formModalApi] = useVbenModal({
|
||||
connectedComponent: FormModalDemo,
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开新增/编辑弹窗
|
||||
* 「添加子科室」复用新增弹窗:只预填 pid,其余字段留空
|
||||
*/
|
||||
const showModal = (data: Record<string, any> = {}, isUpdate = false) => {
|
||||
formModalApi.setData({
|
||||
values: data,
|
||||
update: isUpdate,
|
||||
gridApi,
|
||||
});
|
||||
formModalApi.open();
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除科室(单个或勾选批量)
|
||||
* 物理删除,后端会拦截「有子科室 / 有医生绑定」的情况
|
||||
*/
|
||||
const deleteApi = (row: any) => {
|
||||
let ids = [];
|
||||
if (row) {
|
||||
ids.push(row.id);
|
||||
} else {
|
||||
ids = gridApi.grid.getCheckboxRecords().map((item) => item.id);
|
||||
}
|
||||
deleteDepartment({ ids }).then(() => {
|
||||
message.success('删除成功!');
|
||||
gridApi.query();
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height title="科室管理">
|
||||
<FormModal />
|
||||
<Grid>
|
||||
<template #toolbar-actions>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '新增科室',
|
||||
type: 'primary',
|
||||
icon: 'ant-design:plus-outlined',
|
||||
onClick: showModal.bind(null, {}, false),
|
||||
},
|
||||
]"
|
||||
:drop-down-actions="[
|
||||
{
|
||||
label: '批量删除',
|
||||
icon: 'ant-design:delete-outlined',
|
||||
ifShow: hasTopTableDropDownActions,
|
||||
popConfirm: {
|
||||
title: '确定删除选中的科室吗?',
|
||||
confirm: deleteApi.bind(null, false),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #level="{ row }">
|
||||
<Tag :color="row.level === 1 ? 'blue' : 'cyan'">
|
||||
{{ row.level === 1 ? '一级科室' : '二级科室' }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #feature="{ row }">
|
||||
<Tag :color="row.feature === 1 ? 'gold' : 'default'">
|
||||
{{ row.feature === 1 ? '特色专科' : '普通' }}
|
||||
</Tag>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: '编辑',
|
||||
type: 'link',
|
||||
icon: 'uil:edit',
|
||||
size: 'small',
|
||||
onClick: showModal.bind(null, row, true),
|
||||
},
|
||||
{
|
||||
label: '添加子科室',
|
||||
type: 'link',
|
||||
icon: 'ant-design:apartment-outlined',
|
||||
size: 'small',
|
||||
ifShow: row.level === 1,
|
||||
onClick: showModal.bind(null, { pid: row.id }, false),
|
||||
},
|
||||
]"
|
||||
:drop-down-actions="[
|
||||
{
|
||||
label: '删除',
|
||||
type: 'link',
|
||||
icon: 'ant-design:delete-outlined',
|
||||
size: 'small',
|
||||
popConfirm: {
|
||||
title: '确定删除该科室吗?',
|
||||
confirm: deleteApi.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
Reference in New Issue
Block a user