feat: 优化了若干接口页面,可用性存疑、科室管理、处方发送成功页面

This commit is contained in:
李琦
2026-08-12 12:43:30 +08:00
parent f622743661
commit f15bf10553
9 changed files with 735 additions and 4 deletions

View 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);
}

View File

@@ -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>

View 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',
},
],
};

View 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,
};

View 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,
};

View 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>