删除以前的业务代码

This commit is contained in:
fuenmao
2026-08-24 09:21:34 +08:00
parent fd90aa3e19
commit 3c52039b5b
12 changed files with 1 additions and 3709 deletions
@@ -1,76 +0,0 @@
/**
* @description 药材管理接口
*/
import { moduleRequest } from "@/api/request";
import { ResPage } from "@/api/interface";
// 请求前缀,根据后端路由配置
const http = moduleRequest("/biz/weibingju/medicinal/");
/**
* 药材接口
*/
const medicinalApi = {
/** 获取药材分页列表 */
page(params: { name?: string; pageNum?: number; pageSize?: number }) {
return http.get<ResPage<MedicinalInfo>>("list", params);
},
/** 获取药材下拉字典 */
getDictList() {
return http.get<MedicinalInfo[]>("getmedicinaldictlist");
},
/** 获取药材消耗记录 */
getConsumptionRecords(params: { id: number; pageNum?: number; pageSize?: number }) {
console.log("获取添加记录,请求参数=", params);
return http.get<ResPage<any>>(`consumption-records`, params); // 透传分页参数
},
/** 获取药材添加记录 */
getAdditionRecords(params: { id: number; pageNum?: number; pageSize?: number }) {
return http.get<ResPage<any>>(`addition-records`, params);
},
/** 添加药材 */
addMedicinal(params: AddMedicinalInput) {
return http.post("add", params);
},
/** 修改药材添加记录 */
updateAdditionRecord(params: UpdateMedicinalAddLogInput) {
return http.post(`addition-records`, params);
},
/** 删除药材添加记录 */
deleteAdditionRecord(params: { id: number }) {
return http.post(`addition-delete`, params);
}
};
/** 药材信息 */
export interface MedicinalInfo {
id?: number;
name: string;
unit?: string;
stock?: number;
description?: string;
}
/** 添加药材 */
export interface AddMedicinalInput {
medicinalTypeId: number;
addWeight: number;
totalPrice: number;
medicinalName: string;
}
/** 修改添加记录 */
export interface UpdateMedicinalAddLogInput {
id: number;
addWeight: number;
totalPrice: number;
}
export { medicinalApi };
@@ -1,155 +0,0 @@
import { moduleRequest } from "@/api/request";
import { ResPage } from "@/api/interface";
const http = moduleRequest("/biz/weibingju/prescription/");
// -----------------------------
// 类型定义(对应后端 DTO)
// -----------------------------
/** 人员类型 */
export enum PersonType {
Employee = 1,
Relative = 2
}
/** 处方列表 DTO */
export interface PrescriptionDto {
id: number;
personId: number;
personName: string;
personType: PersonType;
employeeName?: string;
visitDate?: string;
canEdit: boolean;
}
/** 处方药材 DTO */
export interface PrescriptionHerbDto {
Id: number;
medicinalName: string;
amountG: number;
note?: string;
}
/** 人员详情 DTO */
export interface PersonDetailDto {
id?: number;
name: string;
age?: number;
weight?: number;
height?: number;
isEmployee: boolean;
employeeId?: number;
employeeName?: string;
relationShip?: number;
relationShipName?: string;
}
/** 处方详情 DTO */
export interface PrescriptionDetailDto {
id: number;
person: PersonDetailDto;
diagnosisResult: string;
userMedicinaAdvice: string;
visitDate?: string;
herbs: PrescriptionHerbDto[];
canEdit: boolean;
}
/** 保存处方输入 DTO */
export interface SavePrescriptionInput {
id?: number;
person: PersonInputDto;
diagnosisResult: string;
userMedicinaAdvice: string;
visitDate?: string;
herbs: PrescriptionHerbInputDto[];
}
/** 人员输入 DTO */
export interface PersonInputDto {
id?: number;
name: string;
age?: number;
weight?: number;
height?: number;
isEmployee: boolean;
employeeId?: number;
relationShip?: number;
}
/** 处方药材输入 DTO */
export interface PrescriptionHerbInputDto {
Id: number;
amountG: number;
note?: string;
}
/** 人员 DTO(列表用) */
export interface PersonDto {
id: number;
name: string;
age?: number;
employeeId?: number;
}
/** 添加人员输入 DTO */
export interface AddPersonInput {
name: string;
age?: number;
weight?: number;
height?: number;
isEmployee: boolean;
employeeId?: number;
relationShip?: number;
}
/** 关系类型 DTO */
export interface RelationTypeDto {
id: number;
name: string;
}
// -----------------------------
// API 方法(对应后端接口)
// -----------------------------
const prescriptionApi = {
/** 获取处方列表 */
getList(params: any) {
return http.get<ResPage<PrescriptionDto>>("list", params);
},
/** 获取处方详情 */
getDetail(id: number) {
return http.get<PrescriptionDetailDto>(`getprescriptiondetile?id=${id}`);
},
/** 保存处方(新增/编辑) */
save(params: SavePrescriptionInput) {
return http.post<number>("add", params);
},
/** 获取人员列表 */
getPersonList(params: { type?: PersonType; employeeId?: number }) {
return http.get<PersonDto[]>("persons", params);
},
/** 添加人员 */
addPerson(params: AddPersonInput) {
return http.post<number>("addpersons", params);
},
/** 获取关系类型 */
getRelationTypes() {
return http.get<RelationTypeDto[]>("relation-types");
},
/** 获取处方总价 */
getTotalCost() {
return http.get<number>("gettotalcost");
}
};
export { prescriptionApi };
@@ -1,72 +0,0 @@
/**
* @description 微病句统计分析接口
*/
import { moduleRequest } from "@/api/request";
// 请求前缀
const http = moduleRequest("/biz/weibingju/statistics/");
/**
* 统计接口
*/
const statisticsApi = {
/** 获取全量总览数据(不区分年份,含剩余药材库存价值) */
getTotalOverview() {
return http.get<TotalOverviewDto>("overview");
},
/** 获取年度统计数据 */
getYearlyStatistics(params: { year: string }) {
return http.get<YearlyStatisticsDto>("yearly", params);
},
/** 获取月度明细数据 */
getMonthlyDetails(params: { year: string }) {
return http.get<MonthlyDetailDto[]>("monthly", params);
}
};
/** 全量总览数据 DTO */
export interface TotalOverviewDto {
totalCost: number; // 历史处方总花费
totalQty: number; // 历史处方总数量
staffCost: number; // 员工花费
familyCost: number; // 家属花费
staffQty: number; // 员工处方数
familyQty: number; // 家属处方数
materialCost: number; // 药材采购总花费
serviceCount: number; // 累计服务人数
remainingMedicinalValue: number; // 当前剩余药材库存价值
}
/** 年度统计数据 DTO */
export interface YearlyStatisticsDto {
year: string;
totalCost: number; // 总花费
totalQty: number; // 总数量
staffCost: number; // 员工花费
familyCost: number; // 家属花费
staffQty: number; // 员工处方数
familyQty: number; // 家属处方数
materialCost: number; // 药材花费
serviceCount: number; // 服务人数
// 同比趋势(百分比)
totalCostTrend: number; // 处方总花费趋势
totalQtyTrend: number; // 处方总数量趋势
materialCostTrend: number; // 药材花费趋势
serviceCountTrend: number; // 服务人数趋势
}
/** 月度明细数据 DTO */
export interface MonthlyDetailDto {
month: string; // 月份
staffCost: number; // 员工处方花费
familyCost: number; // 家属处方花费
staffQty: number; // 员工处方数量
familyQty: number; // 家属处方数量
materialCost: number; // 药材花费
}
export { statisticsApi };
+1 -4
View File
@@ -1,4 +1,4 @@
/**
/**
* @description
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
@@ -15,6 +15,3 @@
export * from "./organization";
export * from "./ops";
export * from "./WeiBingJu/medicinal";
export * from "./WeiBingJu/prescription";
export * from "./WeiBingJu/statistics";
@@ -1,140 +0,0 @@
<template>
<el-dialog :model-value="visible" @update:model-value="handleUpdateVisible" title="添加药材" width="600px"
destroy-on-close :close-on-click-modal="false">
<el-form :model="form" label-width="120px" ref="formRef" class="mt-2" :rules="formRules">
<!-- 药材下拉 + 未找到按钮 -->
<el-form-item v-if="!isAddingNew" label="药材名称" prop="Id" style="display: flex; align-items: center">
<el-select v-model="form.Id" placeholder="请选择药材" filterable style="flex: 1; margin-right: 8px">
<el-option v-for="item in medicinalList" :key="item.id" :label="item.medicinalName" :value="item.id" />
</el-select>
<el-button type="text" @click="handleNotFound">未找到</el-button>
</el-form-item>
<!-- 新药材输入框 -->
<el-form-item label="新药材名称" v-if="isAddingNew" prop="medicinalName">
<el-input v-model="form.medicinalName" placeholder="请输入新药材名称" />
</el-form-item>
<el-form-item label="添加量(g)" prop="addWeight">
<el-input v-model.number="form.addWeight" type="number" min="0.01" step="0.01" placeholder="请输入添加量" />
</el-form-item>
<!-- 改为总价输入 -->
<el-form-item label="总价" prop="totalPrice">
<el-input v-model.number="form.totalPrice" type="number" min="0" step="0.01" placeholder="请输入总价(元)" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit">确认添加</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, watch, computed } from "vue";
import { medicinalApi } from "@/api/modules/biz/weibingju/medicinal";
import { ElMessage, ElForm, FormRules } from "element-plus";
const props = defineProps({
visible: { type: Boolean, default: false }
});
const emit = defineEmits(["update:visible", "close", "refreshList"]);
const medicinalList = ref<any[]>([]);
const isAddingNew = ref(false);
const formRef = ref<InstanceType<typeof ElForm>>();
const form = reactive({
Id: null as number | null,
addWeight: 0,
// 前端改为 totalPrice
totalPrice: 0,
medicinalName: ""
});
// 动态表单校验(注意 totalPrice 字段)
const formRules = computed<FormRules>(() => ({
Id: [{ required: !isAddingNew.value, message: "请选择药材", trigger: "change" }],
medicinalName: [{ required: isAddingNew.value, message: "请输入新药材名称", trigger: "blur" }],
addWeight: [
{ required: true, message: "请输入添加量", trigger: "blur" },
{ type: "number", min: 0.01, message: "添加量不能小于0.01", trigger: "blur" }
],
totalPrice: [
{ required: true, message: "请输入总价", trigger: "blur" },
{ type: "number", min: 0, message: "总价不能为负", trigger: "blur" }
]
}));
const fetchMedicinalList = async () => {
try {
const res = await medicinalApi.getDictList();
medicinalList.value = res.data || [];
} catch (err) {
medicinalList.value = [];
}
};
watch(
() => props.visible,
async newVal => {
if (newVal) {
handleResetForm();
await fetchMedicinalList();
}
}
);
const handleUpdateVisible = (newVisible: boolean) => {
emit("update:visible", newVisible);
if (!newVisible) {
handleResetForm();
emit("close");
}
};
const handleCancel = () => emit("update:visible", false);
const handleResetForm = () => {
form.Id = null;
form.addWeight = 0;
form.totalPrice = 0;
form.medicinalName = "";
isAddingNew.value = false;
formRef.value?.resetFields();
};
const handleNotFound = () => {
isAddingNew.value = true;
form.Id = null;
};
const handleSubmit = async () => {
if (!formRef.value) return;
try {
await formRef.value.validate();
} catch {
return;
}
// 计算单价,并校验 addWeight > 0
if (!form.addWeight || form.addWeight <= 0) {
ElMessage.warning("添加量需大于 0");
return;
}
const payload: any = {
Id: form.Id,
addWeight: form.addWeight,
totalPrice: form.totalPrice,
medicinalName: form.medicinalName
};
try {
await medicinalApi.addMedicinal(payload);
ElMessage.success("添加成功");
emit("update:visible", false);
emit("refreshList");
} catch (err: any) {
}
};
</script>
@@ -1,219 +0,0 @@
<template>
<el-dialog :model-value="props.visible" :title="`${props.medicinalName} - 添加记录`" width="1200px"
@close="handleDialogClose" @update:model-value="handleUpdateVisible" append-to-body
:body-class="'el-dialog__body-new'" :close-on-click-modal="false">
<ProTable ref="proTableRef" title="添加记录" :columns="columns" :request-api="getAdditionRecordsApi" :row-key="rowKey"
:border="true" :show-selection="false">
<!-- 行内列 slot:addWeight -->
<template #addWeight="{ row }">
<template v-if="editingId === row.id">
<el-input-number v-model.number="editModel.addWeight" :min="0.01" :step="0.01" controls-position="right"
size="small" />
</template>
<template v-else>
{{ row.addWeight }}
</template>
</template>
<!-- unitPrice 列:只展示单价,不编辑 -->
<template #unitPrice="{ row }">
{{ (row.unitPrice ?? 0).toFixed(5) }}
</template>
<!-- totalPrice 列:查看时显示合计,编辑时可以修改 -->
<template #totalPrice="{ row }">
<template v-if="editingId === row.id">
<el-input-number v-model.number="editModel.totalPrice" :min="0" :step="0.01" controls-position="right"
size="small" placeholder="输入总价(元)" />
</template>
<template v-else>
{{ row.totalPrice }}
</template>
</template>
<!-- addTime 格式化 -->
<template #addTime="{ row }">
{{ row.addTime ? formatDate(row.addTime) : "-" }}
</template>
<!-- 操作列 -->
<template #operation="{ row }">
<template v-if="editingId === row.id">
<el-button size="small" type="primary" @click="saveRowEdit(row)" :loading="saving">保存</el-button>
<el-button size="small" @click="cancelRowEdit">取消</el-button>
</template>
<template v-else>
<el-button v-if="row.canEdit" type="text" size="small" @click="startRowEdit(row)">编辑</el-button>
<el-button v-if="row.canEdit" type="text" size="small" style="color: #ff4d4f"
@click="handleDeleteRecord(row.id)">
删除
</el-button>
</template>
</template>
</ProTable>
</el-dialog>
</template>
<script setup lang="ts">
/**
* 单行行内编辑版本(总价 totalPrice 输入):
* - 点击「编辑」:将该行复制到 editModel(editModel.addWeight & editModel.totalPrice),记录 editingId(只允许单行编辑)
* - 取消:不调用后端,直接清除 editingId(恢复显示)
*/
import { defineProps, defineEmits, ref, reactive, watch, nextTick } from "vue";
import ProTable from "@/components/ProTable/index.vue";
import { medicinalApi } from "@/api";
import { ElMessage, ElMessageBox } from "element-plus";
const props = defineProps({
visible: { type: Boolean, default: false },
id: { type: Number, required: true },
medicinalName: { type: String, default: "药材" }
});
const emit = defineEmits(["update:visible", "close", "refreshList"]);
// proTable 引用(用于 refresh)
const proTableRef = ref<any | null>(null);
// 当前正在编辑的行 id(单行)
const editingId = ref<number | null>(null);
// 编辑模型(只存一行):注意 totalPrice
const editModel = reactive({
addWeight: 0,
totalPrice: 0
});
// 保存中标记,避免重复提交
const saving = ref(false);
// 列定义,slotName 使用 prop 名称(与 ProTable slot 对应)
const columns = [
{ prop: "addWeight", label: "添加量(g)", minWidth: 120, slotName: "addWeight" },
{ prop: "unitPrice", label: "单价(元/g)", minWidth: 120, slotName: "unitPrice" },
{ prop: "totalPrice", label: "总价(元)", minWidth: 120, slotName: "totalPrice" },
{ prop: "addTime", label: "添加时间", minWidth: 200, slotName: "addTime" },
{ prop: "addUserName", label: "添加人员", minWidth: 150 },
{ prop: "operation", label: "操作", width: 180, fixed: "right", slotName: "operation" }
];
const rowKey = (row: any) => row.id;
// 打开弹窗时刷新表格
watch(
() => props.visible,
async val => {
if (val && props.id) {
console.log("打开添加记录弹窗,药材ID=", props.id);
await nextTick();
proTableRef.value?.refresh?.();
}
},
{ immediate: true }
);
// 将 ProTable 的请求透传到后端(ProTable 会带上 pageNum/pageSize 等)
const getAdditionRecordsApi = (params: any) => {
return medicinalApi.getAdditionRecords({
id: props.id,
...params
});
};
// 开始编辑:把当前行复制到 editModel(addWeight 和 totalPrice)
const startRowEdit = (row: any) => {
if (editingId.value !== null && editingId.value !== row.id) {
ElMessage.warning("请先保存或取消当前正在编辑的行");
return;
}
editingId.value = row.id;
editModel.addWeight = row.addWeight ?? 0;
// 后端返回的是 unitPrice,前端要显示/编辑 totalPrice = unitPrice * addWeight
editModel.totalPrice = row.totalPrice;
};
// 取消编辑:清空 editingId(恢复显示)
const cancelRowEdit = () => {
editingId.value = null;
editModel.addWeight = 0;
editModel.totalPrice = 0;
};
// 保存编辑:计算 unitPrice = totalPrice / addWeight -> 发送到后端
const saveRowEdit = async (row: any) => {
// 基本校验
if (!editModel.addWeight || editModel.addWeight <= 0) {
ElMessage.warning("添加量需大于 0");
return;
}
if (editModel.totalPrice < 0) {
ElMessage.warning("总价不能为负");
return;
}
try {
saving.value = true;
await medicinalApi.updateAdditionRecord({
id: row.id,
addWeight: editModel.addWeight,
totalPrice: editModel.totalPrice
});
ElMessage.success("保存成功");
// 刷新表格并退出编辑
await proTableRef.value?.refresh?.();
emit("refreshList");
cancelRowEdit();
} catch (err: any) {
} finally {
saving.value = false;
}
};
// 删除记录
const handleDeleteRecord = async (id: number) => {
ElMessageBox.confirm("确定要删除这条添加记录吗?删除后不可恢复!", "确认删除", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning"
})
.then(async () => {
try {
await medicinalApi.deleteAdditionRecord({ id: id });
ElMessage.success("删除成功");
await proTableRef.value?.refresh?.();
emit("refreshList");
} catch (err: any) {
}
})
.catch(() => {
/* 用户取消删除 */
});
};
// 格式化时间
const formatDate = (dateStr: string) => {
const d = new Date(String(dateStr).replace(" ", "T"));
return isNaN(d.getTime()) ? dateStr : d.toLocaleString();
};
// 处理弹窗关闭
const handleDialogClose = () => {
editingId.value = null;
editModel.addWeight = 0;
editModel.totalPrice = 0;
emit("update:visible", false);
emit("close");
};
const handleUpdateVisible = (newVal: boolean) => {
if (!newVal) handleDialogClose();
};
</script>
<style lang="scss">
.el-dialog__body-new {
height: 550px !important;
}
</style>
@@ -1,120 +0,0 @@
<template>
<el-dialog :model-value="props.visible" :title="`${props.medicinalName} - 消耗记录`" width="800px"
@close="handleDialogClose" @update:model-value="handleUpdateVisible" append-to-body
:body-class="'el-dialog__body-new'" :close-on-click-modal="false">
<!-- 统一使用ProTable实现列表(带分页),与AdditionRecordDialog风格保持一致 -->
<ProTable ref="proTableRef" title="消耗记录列表" :columns="columns" :request-api="getConsumptionRecordsApi"
:row-key="rowKey" :show-selection="false" :border="true">
<!-- 人员列:包含家属标记的自定义渲染 -->
<template #personName="{ row }">
<span>{{ row.personName }}</span>
<el-tag size="small" type="info" v-if="row.isRelative" style="margin-left: 8px">
家属({{ row.employeeName || "未知员工" }})
</el-tag>
</template>
<!-- 使用时间:格式化渲染 -->
<template #useTime="{ row }">
{{ row.useTime ? formatDate(row.useTime) : "-" }}
</template>
</ProTable>
</el-dialog>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, ref, watch, nextTick } from "vue";
import ProTable from "@/components/ProTable/index.vue";
import { medicinalApi } from "@/api";
import { ElMessage, ElMessageBox } from "element-plus";
/** 1. 定义Props(与父组件传参匹配,参考AdditionRecordDialog) */
const props = defineProps({
visible: {
type: Boolean,
default: false
},
id: { // 对应父组件传的「currentMedicinalId」,与AdditionRecordDialog的id参数统一
type: Number,
required: true
},
medicinalName: {
type: String,
default: "药材"
}
});
/** 2. 定义Emits(与AdditionRecordDialog事件机制统一) */
const emit = defineEmits(["update:visible", "close", "refreshList"]);
/** 3. 状态管理(ProTable引用 + 无编辑状态,仅列表) */
const proTableRef = ref<any | null>(null); // ProTable实例引用,用于刷新列表
/** 4. 列配置(与消耗记录字段匹配,仅保留展示列) */
const columns = [
{
prop: "amountG",
label: "使用量(g)",
minWidth: 100
},
{
prop: "personName",
label: "使用人员",
minWidth: 250,
slotName: "personName" // 关联自定义模板
},
{
prop: "useTime",
label: "使用时间",
minWidth: 150,
slotName: "useTime" // 关联时间格式化模板
}
];
/** 5. 行唯一标识(与后端返回的主键匹配) */
const rowKey = (row: any) => row.id;
/** 6. 接口请求(透传ProTable分页参数,与AdditionRecordDialog逻辑统一) */
const getConsumptionRecordsApi = (params: any) => {
// 调用药材消耗记录接口,携带「药材ID」和「分页参数」
return medicinalApi.getConsumptionRecords({
id: props.id, // 药材ID(必传)
...params // 分页参数(pageNum、pageSize等,从ProTable传入)
});
};
/** 7. 时间格式化工具(与AdditionRecordDialog格式统一) */
const formatDate = (dateStr: string) => {
const d = new Date(String(dateStr).replace(" ", "T"));
return isNaN(d.getTime()) ? dateStr : d.toLocaleString();
};
/** 8. 弹窗显示/隐藏监听(弹窗显示时刷新列表,与AdditionRecordDialog逻辑统一) */
watch(
() => props.visible,
async (isVisible) => {
if (isVisible && props.id) {
await nextTick(); // 等待ProTable渲染完成
proTableRef.value?.refresh?.(); // 刷新列表数据
}
},
{ immediate: true } // 初始加载时触发
);
/** 9. 弹窗关闭逻辑(与AdditionRecordDialog统一,清空状态+通知父组件) */
const handleDialogClose = () => {
emit("update:visible", false); // 通知父组件更新弹窗状态
emit("close"); // 触发关闭事件(可选)
};
/** 10. 弹窗状态更新回调(与Element Plus Dialog事件匹配) */
const handleUpdateVisible = (newVal: boolean) => {
if (!newVal) { // 仅当弹窗关闭时执行清理逻辑
handleDialogClose();
}
};
</script>
<style lang="scss">
.el-dialog__body-new {
height: 550px !important;
}
</style>
@@ -1,87 +0,0 @@
<template>
<div class="table-box">
<ProTable ref="proTable" title="药材列表" :columns="columns" :request-api="medicinalApi.page">
<template #tableHeader>
<!-- 新增按钮:保持框架一致的 s-button 写法 -->
<s-button suffix="药材" @click="onOpenAdd" />
</template>
<template #operation="scope">
<s-button link :opt="FormOptEnum.VIEW" size="small" @click="handleViewConsumption(scope.row)"> 查看消耗记录
</s-button>
<s-button link :opt="FormOptEnum.VIEW" size="small" class="ml-2" @click="handleViewAddition(scope.row)">
查看添加记录 </s-button>
</template>
</ProTable>
<!-- 消耗记录弹窗 -->
<consumption-record-dialog v-model:visible="showConsumptionDialog" :id="currentMedicinalId"
:medicinal-name="currentMedicinalName" />
<!-- 添加记录弹窗 -->
<addition-record-dialog :visible="showAdditionDialog" :id="currentMedicinalId"
:medicinal-name="currentMedicinalName" @update:visible="val => (showAdditionDialog = val)"
@refresh-list="handleRefresh" />
<!-- 新增药材弹窗 -->
<add-medicinal-dialog v-model:visible="showAddMedicinalDialog" @refresh-list="handleRefresh" />
</div>
</template>
<script setup lang="tsx" name="sysModule">
import { medicinalApi, MedicinalInfo } from "@/api";
import { ref } from "vue";
import ProTable from "@/components/ProTable/index.vue";
import type { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { FormOptEnum } from "@/enums";
// 子组件
import ConsumptionRecordDialog from "./components/ConsumptionRecordDialog.vue";
import AdditionRecordDialog from "./components/AdditionRecordDialog.vue";
import AddMedicinalDialog from "./components/AddMedicinalDialog.vue";
const proTable = ref<ProTableInstance | null>(null);
const showConsumptionDialog = ref(false);
const showAdditionDialog = ref(false);
const showAddMedicinalDialog = ref(false);
const currentMedicinalId = ref<number | null>(null);
const currentMedicinalName = ref<string>("");
const columns: ColumnProps<MedicinalInfo>[] = [
{ type: "selection", fixed: "left", width: 80 },
{ prop: "medicinalName", label: "药材名称", search: { el: "input" } },
{ prop: "totalUsedWeight", label: "使用量(g)" },
{ prop: "stock", label: "药材库存(g)" },
{ prop: "usedCost", label: "预估使用成本" },
{ prop: "remainingValue", label: "预估剩余价值" },
{ prop: "operation", label: "操作", width: 240, fixed: "right" }
];
// 打开新增药材弹窗
function onOpenAdd() {
showAddMedicinalDialog.value = true;
}
// 查看消耗记录
function handleViewConsumption(row: MedicinalInfo) {
currentMedicinalId.value = row.Id ?? (row as any).id ?? null;
currentMedicinalName.value = row.medicinalName ?? "";
showConsumptionDialog.value = true;
}
// 查看添加记录
function handleViewAddition(row: MedicinalInfo) {
console.log("查看添加记录", row);
// 直接设置正确的ID和名称,然后显示弹窗
currentMedicinalId.value = row.id ?? (row as any).id ?? null;
currentMedicinalName.value = row.medicinalName ?? "";
showAdditionDialog.value = true;
}
// 刷新列表
function handleRefresh() {
proTable.value?.refresh?.();
}
</script>
@@ -1,983 +0,0 @@
<template>
<el-dialog :model-value="visible" :title="dialogTitle" width="1000px" destroy-on-close @close="handleClose"
:body-class="'modal-class'" @update:model-value="handleVisibleChange" :close-on-click-modal="false">
<!-- 表单根节点:查看模式禁用,新增/编辑模式可交互 -->
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" class="prescription-form"
:disabled="isViewMode">
<!-- 1. 人员信息 -->
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-title">人员信息</div>
</template>
<!-- 人员类型 -->
<el-form-item label="人员类型" prop="personType">
<el-radio-group v-model="formData.personType" @change="handlePersonTypeChange" :disabled="isViewMode">
<el-radio :label="1">公司员工</el-radio>
<el-radio :label="2">员工家属</el-radio>
</el-radio-group>
</el-form-item>
<!-- 公司员工模式 -->
<template v-if="formData.personType === 1">
<!-- 员工姓名 -->
<el-form-item label="员工姓名" prop="employeeInfo">
<div class="flex items-center gap-2">
<el-select v-if="showEmployeeSelect" v-model="formData.employeeId" placeholder="请选择员工" filterable
:loading="employeeLoading" class="flex-1" @change="handleEmployeeChange" @clear="handleEmployeeClear"
:disabled="isViewMode">
<el-option v-for="item in employeeOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-button v-if="!isViewMode" type="primary" size="small" @click="handleOpenAddEmployeeDialog">
新增员工
</el-button>
</div>
<el-input v-if="!showEmployeeSelect && !formData.employeeId" v-model="formData.newEmployeeName"
placeholder="请输入员工姓名" class="mt-2" :disabled="isViewMode" />
</el-form-item>
<!-- 员工性质 -->
<el-form-item label="员工性质" prop="type">
<el-radio-group v-model="formData.type" :disabled="isViewMode">
<el-radio :label="1">领导</el-radio>
<el-radio :label="2">普通员工</el-radio>
</el-radio-group>
</el-form-item>
</template>
<!-- 员工家属模式 -->
<template v-if="formData.personType === 2">
<!-- 关联员工 -->
<el-form-item label="关联员工" prop="relatedEmployeeInfo">
<div class="flex items-center gap-2">
<el-select v-if="showRelatedEmployeeSelect" v-model="formData.relatedEmployeeId" placeholder="请选择关联员工"
filterable :loading="employeeLoading" class="flex-1" @change="handleRelatedEmployeeChange"
@clear="handleRelatedEmployeeClear" :disabled="isViewMode">
<el-option v-for="item in employeeOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<!-- 新增/编辑模式显示"新增关联员工"按钮 -->
<el-button v-if="!isViewMode" type="primary" size="small" @click="handleOpenAddRelatedEmployeeDialog">
新增员工
</el-button>
</div>
<el-input v-if="!showRelatedEmployeeSelect && !formData.relatedEmployeeId"
v-model="formData.newRelatedEmployeeName" placeholder="请输入关联员工姓名" class="mt-2" :disabled="isViewMode" />
</el-form-item>
<!-- 家属姓名 -->
<el-form-item label="家属姓名" prop="relativeInfo">
<div class="flex items-center gap-2">
<el-select v-if="showRelativeSelect && (formData.relatedEmployeeId || formData.newRelatedEmployeeName)"
v-model="formData.relativeId" placeholder="请选择家属" filterable :loading="relativeLoading" class="flex-1"
@change="handleRelativeChange" @clear="handleRelativeClear" :disabled="isViewMode">
<el-option v-for="item in relativeOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-input v-else v-model="formData.newRelativeName" placeholder="请输入家属姓名" class="flex-1"
:disabled="!formData.relatedEmployeeId && !formData.newRelatedEmployeeName || isViewMode" />
<!-- 新增/编辑模式显示"新增家属"按钮 -->
<el-button
v-if="!isViewMode && showRelativeSelect && formData.relatedEmployeeId && !formData.newRelatedEmployeeName"
type="primary" size="small" @click="handleOpenAddRelativeDialog">
新增家属
</el-button>
</div>
</el-form-item>
<!-- 亲属关系 -->
<el-form-item label="亲属关系" prop="relationShip">
<el-select v-model="formData.relationShip" placeholder="请选择亲属关系" :disabled="isViewMode">
<el-option v-for="item in relationTypes" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</template>
<!-- 出生日期、体重、身高 -->
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="出生日期" prop="birthday">
<el-date-picker v-model="formData.birthday" type="date" placeholder="请选择出生日期" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" class="w-full" :disabled="isViewMode" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="当前体重(kg)" prop="weight">
<el-input-number v-model="formData.weight" :min="0" :max="500" :precision="1" placeholder="请输入体重"
class="w-full" :disabled="isViewMode" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="身高(cm)" prop="height">
<el-input-number v-model="formData.height" :min="0" :max="300" :precision="1" placeholder="请输入身高"
class="w-full" :disabled="isViewMode" />
</el-form-item>
</el-col>
</el-row>
</el-card>
<!-- 2. 药材消耗 -->
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-title">
药材消耗
<!-- 新增/编辑模式显示"添加药材"按钮 -->
<el-button v-if="!isViewMode" type="primary" size="small" class="ml-4" @click="addHerb">
添加药材
</el-button>
</div>
</template>
<el-table :data="formData.herbs" border>
<el-table-column label="药材名称--剩余量" width="200">
<template #default="{ row, $index }">
<el-select v-model="row.id" placeholder="请选择药材" filterable :loading="medicinalLoading"
@change="handleMedicinalChange($index)" :disabled="isViewMode">
<el-option v-for="item in medicinalOptions" :key="item.id"
:label="`${item.medicinalName}--${item.stock}g`" :value="item.id" />
</el-select>
</template>
</el-table-column>
<el-table-column label="消耗(g)" width="200">
<template #default="{ row }">
<el-input-number v-model="row.amountG" :min="0" :precision="2" placeholder="消耗量" class="w-full"
:disabled="isViewMode" />
</template>
</el-table-column>
<el-table-column label="备注" min-width="80">
<template #default="{ row }">
<el-input v-model="row.note" placeholder="请输入备注" :disabled="isViewMode" />
</template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{ $index }">
<!-- 新增/编辑模式显示"移除"按钮 -->
<el-button v-if="!isViewMode" type="danger" size="small" @click="removeHerb($index)">
移除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 3. 诊断信息 -->
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-title">诊断信息</div>
</template>
<el-form-item label="诊断结果" prop="diagnosisResult">
<el-input v-model="formData.diagnosisResult" type="textarea" :rows="3" placeholder="请输入诊断结果"
:disabled="isViewMode" />
</el-form-item>
<el-form-item label="用药建议" prop="userMedicinaAdvice">
<el-input v-model="formData.userMedicinaAdvice" type="textarea" :rows="3" placeholder="请输入用药建议"
:disabled="isViewMode" />
</el-form-item>
</el-card>
<!-- 4. 就诊信息 -->
<el-card class="form-section" shadow="never">
<template #header>
<div class="section-title">就诊信息</div>
</template>
<el-form-item label="就诊时间" prop="visitDate">
<el-date-picker v-model="formData.visitDate" type="datetime" placeholder="请选择就诊时间" format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss" class="w-full" :disabled="isViewMode" />
</el-form-item>
</el-card>
</el-form>
<!-- 底部按钮 -->
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<!-- 新增/编辑模式显示提交按钮 -->
<el-button v-if="!isViewMode" type="primary" :loading="submitLoading" @click="handleSubmit">
{{ isEdit ? '更新' : '保存' }}
</el-button>
</div>
</template>
<!-- 新增员工弹窗(仅在新增/编辑模式显示) -->
<el-dialog v-if="!isViewMode" v-model="showAddEmployeeDialog" title="新增员工" width="400px">
<el-form :model="newEmployeeForm" label-width="80px" ref="newEmployeeFormRef">
<el-form-item label="员工姓名" prop="name">
<el-input v-model="newEmployeeForm.name" placeholder="请输入员工姓名" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddEmployeeDialog = false">取消</el-button>
<el-button type="primary" @click="confirmAddEmployee">确定</el-button>
</template>
</el-dialog>
<!-- 新增关联员工弹窗(仅在新增/编辑模式显示) -->
<el-dialog v-if="!isViewMode" v-model="showAddRelatedEmployeeDialog" title="新增关联员工" width="400px">
<el-form :model="newRelatedEmployeeForm" label-width="80px" ref="newRelatedEmployeeFormRef">
<el-form-item label="员工姓名" prop="name">
<el-input v-model="newRelatedEmployeeForm.name" placeholder="请输入员工姓名" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddRelatedEmployeeDialog = false">取消</el-button>
<el-button type="primary" @click="confirmAddRelatedEmployee">确定</el-button>
</template>
</el-dialog>
<!-- 新增家属弹窗(仅在新增/编辑模式显示) -->
<el-dialog v-if="!isViewMode" v-model="showAddRelativeDialog" title="新增家属" width="400px">
<el-form :model="newRelativeForm" label-width="80px" ref="newRelativeFormRef">
<el-form-item label="家属姓名" prop="name">
<el-input v-model="newRelativeForm.name" placeholder="请输入家属姓名" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddRelativeDialog = false">取消</el-button>
<el-button type="primary" @click="confirmAddRelative">确定</el-button>
</template>
</el-dialog>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { prescriptionApi } from '@/api'
import { medicinalApi } from '@/api/modules/biz/WeiBingJu/medicinal'
// 接口定义
interface Employee {
id: string | number;
name: string;
birthday?: string;
height?: number;
type?: number;
}
interface Relative {
id: string | number;
name: string;
employeeId?: string | number;
birthday?: string;
height?: number;
relationShip?: number;
}
interface Medicinal { id: number | string; medicinalName: string; stock: number }
interface Herb { id?: number | string; medicinalName: string; amountG: number; note: string }
// Props定义:区分新增(无id)、编辑(有id且isEdit=true)、查看(有id且isEdit=false)
interface Props {
visible: boolean;
prescriptionId?: number;
isEdit: boolean
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
prescriptionId: undefined,
isEdit: false
})
const emit = defineEmits<{
"update:visible": [value: boolean];
"refresh-list": [];
}>()
// 核心模式判断:查看模式 = 有处方ID且非编辑
const isViewMode = computed(() => !!props.prescriptionId && !props.isEdit)
// 弹窗标题:根据模式动态显示
const dialogTitle = computed(() => {
if (isViewMode.value) return '查看就诊处方'
return props.isEdit ? '编辑就诊处方' : '新增就诊处方'
})
// 表单引用 & 状态
const formRef = ref<FormInstance>()
const newEmployeeFormRef = ref<FormInstance>()
const newRelatedEmployeeFormRef = ref<FormInstance>()
const newRelativeFormRef = ref<FormInstance>()
const submitLoading = ref(false)
const isEdit = computed(() => props.isEdit)
// 表单数据
const formData = ref({
personType: 1,
type: undefined as number | undefined,
employeeId: undefined as string | number | undefined,
newEmployeeName: '',
relatedEmployeeId: undefined as string | number | undefined,
newRelatedEmployeeName: '',
relativeId: undefined as string | number | undefined,
newRelativeName: '',
relationShip: undefined as number | undefined,
birthday: undefined as string | undefined,
weight: undefined as number | undefined,
height: undefined as number | undefined,
herbs: [] as Herb[],
diagnosisResult: '',
userMedicinaAdvice: '',
visitDate: ''
})
// 表单校验规则
const formRules: FormRules = {
personType: [{ required: true, message: '请选择人员类型', trigger: 'change' }],
type: [{
required: true,
validator: (rule, value, callback) => {
if (formData.value.personType === 1 && !formData.value.type) {
callback(new Error('请选择员工性质'))
} else {
callback()
}
},
trigger: 'change'
}],
employeeInfo: [{
required: true,
validator: (rule, value, callback) => {
if (formData.value.personType === 1) {
if (!formData.value.employeeId && !formData.value.newEmployeeName) {
callback(new Error('请选择或输入员工姓名'))
} else {
callback()
}
} else {
callback()
}
},
trigger: ['change', 'blur']
}],
relatedEmployeeInfo: [{
required: true,
validator: (rule, value, callback) => {
if (formData.value.personType === 2) {
if (!formData.value.relatedEmployeeId && !formData.value.newRelatedEmployeeName) {
callback(new Error('请选择或输入关联员工姓名'))
} else {
callback()
}
} else {
callback()
}
},
trigger: ['change', 'blur']
}],
relativeInfo: [{
required: true,
validator: (rule, value, callback) => {
if (formData.value.personType === 2) {
if (!formData.value.relativeId && !formData.value.newRelativeName) {
callback(new Error('请选择或输入家属姓名'))
} else {
callback()
}
} else {
callback()
}
},
trigger: ['change', 'blur']
}],
relationShip: [{
required: formData.value.personType === 2,
message: '请选择亲属关系',
trigger: 'change'
}],
visitDate: [{ required: true, message: '请选择就诊时间', trigger: 'change' }],
diagnosisResult: [{ required: true, message: '请输入诊断结果', trigger: 'blur' }]
}
// 数据源 & 弹窗状态
const employeeOptions = ref<Employee[]>([])
const employeeLoading = ref(false)
const showEmployeeSelect = ref(true)
const showRelatedEmployeeSelect = ref(true)
const relativeOptions = ref<Relative[]>([])
const relativeLoading = ref(false)
const showRelativeSelect = ref(true)
const relationTypes = ref<{ id: number; name: string }[]>([])
const medicinalOptions = ref<Medicinal[]>([])
const medicinalLoading = ref(false)
const showAddEmployeeDialog = ref(false)
const showAddRelatedEmployeeDialog = ref(false)
const showAddRelativeDialog = ref(false)
const newEmployeeForm = ref({ name: '' })
const newRelatedEmployeeForm = ref({ name: '' })
const newRelativeForm = ref({ name: '' })
// 本地临时人员集合
const localCreatedPersons = ref<Array<{
tempId: string
name: string
isEmployee: boolean
employeeTempId?: string
employeeId?: number
relationShip?: number
}>>([])
// 生成临时ID
const genTempId = (prefix = 'tmp') => `${prefix}_${Date.now()}_${Math.floor(Math.random() * 1000)}`
// 检查名称重复
const checkNameDuplicate = (name: string, isEmployee: boolean) => {
const normalizedName = name.trim()
if (isEmployee) {
return employeeOptions.value.some(item =>
(item.name || '').trim() === normalizedName ||
localCreatedPersons.value.some(p => p.isEmployee && p.name.trim() === normalizedName)
)
} else {
return relativeOptions.value.some(item =>
(item.name || '').trim() === normalizedName ||
localCreatedPersons.value.some(p => !p.isEmployee && p.name.trim() === normalizedName)
)
}
}
// 加载数据接口
const fetchEmployees = async (keyword?: string) => {
try {
employeeLoading.value = true
const params: any = { type: 1 }
if (keyword) params.keyword = keyword
const res: any = await prescriptionApi.getPersonList(params)
employeeOptions.value = (res && res.data) ? res.data : []
} catch (err) {
console.error('获取员工列表失败', err)
employeeOptions.value = []
} finally {
employeeLoading.value = false
}
}
const loadRelatives = async (employeeId: number | string) => {
try {
relativeLoading.value = true
if (typeof employeeId === 'string' && employeeId.startsWith('tmp_')) {
relativeOptions.value = []
showRelativeSelect.value = false
ElMessage.info('该员工为本地新增,暂无家属数据,请手动输入家属姓名')
} else {
const res: any = await prescriptionApi.getPersonList({ type: 2, employeeId: Number(employeeId) })
relativeOptions.value = (res && res.data) ? res.data : []
showRelativeSelect.value = relativeOptions.value.length > 0
}
} catch (err) {
console.error('加载家属失败', err)
relativeOptions.value = []
showRelativeSelect.value = false
} finally {
relativeLoading.value = false
}
}
const fetchRelationTypes = async () => {
try {
const res: any = await prescriptionApi.getRelationTypes()
relationTypes.value = (res && res.data) ? res.data : []
} catch (err) {
relationTypes.value = [
{ id: 1, name: '配偶' },
{ id: 2, name: '子女' },
{ id: 3, name: '父母' },
{ id: 4, name: '兄弟姐妹' },
{ id: 5, name: '其他' }
]
console.error('获取关系类型失败', err)
}
}
const loadAllMedicinals = async () => {
try {
medicinalLoading.value = true
const res: any = await medicinalApi.getDictList()
medicinalOptions.value = (res && res.data) ? res.data : []
} catch (err) {
console.error('加载药材失败', err)
medicinalOptions.value = []
} finally {
medicinalLoading.value = false
}
}
// 选择回调
const handleEmployeeChange = (selectedId: string | number | undefined) => {
if (selectedId && !isViewMode.value) { // 仅在非查看模式执行
formData.value.newEmployeeName = ''
showEmployeeSelect.value = true
const selectedEmployee = employeeOptions.value.find(item => item.id === selectedId)
if (selectedEmployee) {
formData.value.birthday = selectedEmployee.birthday
formData.value.height = selectedEmployee.height
formData.value.type = selectedEmployee.type
}
}
}
const handleEmployeeClear = () => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.newEmployeeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.type = undefined
}
}
const handleRelatedEmployeeChange = (selectedId: string | number | undefined) => {
if (selectedId && !isViewMode.value) { // 仅在非查看模式执行
formData.value.newRelatedEmployeeName = ''
showRelatedEmployeeSelect.value = true
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
loadRelatives(selectedId)
}
}
const handleRelatedEmployeeClear = () => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.newRelatedEmployeeName = ''
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
relativeOptions.value = []
}
}
const handleRelativeChange = (selectedId: string | number | undefined) => {
if (selectedId && !isViewMode.value) { // 仅在非查看模式执行
formData.value.newRelativeName = ''
showRelativeSelect.value = true
const selectedRelative = relativeOptions.value.find(item => item.id === selectedId)
if (selectedRelative) {
formData.value.birthday = selectedRelative.birthday
formData.value.height = selectedRelative.height
formData.value.relationShip = selectedRelative.relationShip
}
}
}
const handleRelativeClear = () => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
}
}
const handleMedicinalChange = (index: number) => {
if (!isViewMode.value) { // 仅在非查看模式执行
const selectedMedicinalId = formData.value.herbs[index].id;
if (selectedMedicinalId) {
const sel = medicinalOptions.value.find(m => m.id === selectedMedicinalId);
formData.value.herbs[index].medicinalName = sel ? sel.medicinalName : '';
} else {
formData.value.herbs[index].medicinalName = '';
}
}
};
const handlePersonTypeChange = () => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.employeeId = undefined
formData.value.newEmployeeName = ''
formData.value.relatedEmployeeId = undefined
formData.value.newRelatedEmployeeName = ''
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
formData.value.relationShip = undefined
formData.value.birthday = undefined
formData.value.height = undefined
showEmployeeSelect.value = true
showRelatedEmployeeSelect.value = true
showRelativeSelect.value = true
}
}
const addHerb = () => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.herbs.push({ id: undefined, medicinalName: '', amountG: 0, note: '' })
}
}
const removeHerb = (index: number) => {
if (!isViewMode.value) { // 仅在非查看模式执行
formData.value.herbs.splice(index, 1)
}
}
// 新增员工相关
const handleOpenAddEmployeeDialog = () => {
if (!isViewMode.value) {
formData.value.employeeId = undefined
formData.value.newEmployeeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
newEmployeeForm.value.name = ''
showAddEmployeeDialog.value = true
}
}
const confirmAddEmployee = async () => {
if (!newEmployeeFormRef.value || isViewMode.value) return
try {
await newEmployeeFormRef.value.validate()
const name = newEmployeeForm.value.name.trim()
if (checkNameDuplicate(name, true)) {
ElMessage.warning('该员工姓名已存在,请更换姓名')
return
}
const tempId = genTempId('tmp_emp')
employeeOptions.value.unshift({ id: tempId, name })
localCreatedPersons.value.push({ tempId, name, isEmployee: true })
formData.value.employeeId = tempId
formData.value.newEmployeeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
showEmployeeSelect.value = true
showAddEmployeeDialog.value = false
newEmployeeForm.value.name = ''
ElMessage.success('员工已添加')
} catch (err) { }
}
// 新增关联员工相关
const handleOpenAddRelatedEmployeeDialog = () => {
if (!isViewMode.value) {
formData.value.relatedEmployeeId = undefined
formData.value.newRelatedEmployeeName = ''
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
newRelatedEmployeeForm.value.name = ''
showAddRelatedEmployeeDialog.value = true
}
}
const confirmAddRelatedEmployee = async () => {
if (!newRelatedEmployeeFormRef.value || isViewMode.value) return
try {
await newRelatedEmployeeFormRef.value.validate()
const name = newRelatedEmployeeForm.value.name.trim()
if (checkNameDuplicate(name, true)) {
ElMessage.warning('该员工姓名已存在,请更换姓名')
return
}
const tempId = genTempId('tmp_emp')
employeeOptions.value.unshift({ id: tempId, name })
localCreatedPersons.value.push({ tempId, name, isEmployee: true })
formData.value.relatedEmployeeId = tempId
formData.value.newRelatedEmployeeName = ''
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
showRelatedEmployeeSelect.value = true
showAddRelatedEmployeeDialog.value = false
newRelatedEmployeeForm.value.name = ''
loadRelatives(tempId)
ElMessage.success('关联员工已添加')
} catch (err) { }
}
// 新增家属相关
const handleOpenAddRelativeDialog = () => {
if (!isViewMode.value) {
formData.value.relativeId = undefined
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
newRelativeForm.value.name = ''
showAddRelativeDialog.value = true
}
}
const confirmAddRelative = async () => {
if (!newRelativeFormRef.value || isViewMode.value) return
try {
await newRelativeFormRef.value.validate()
const name = newRelativeForm.value.name.trim()
const employeeId = formData.value.relatedEmployeeId
if (!employeeId) {
ElMessage.warning('请先选择关联员工')
return
}
if (checkNameDuplicate(name, false)) {
ElMessage.warning('该家属姓名已存在,请更换姓名')
return
}
const tempId = genTempId('tmp_rel')
relativeOptions.value.unshift({ id: tempId, name, employeeId })
localCreatedPersons.value.push({
tempId,
name,
isEmployee: false,
employeeTempId: typeof employeeId === 'string' && employeeId.startsWith('tmp_') ? employeeId : undefined,
employeeId: typeof employeeId === 'number' ? employeeId : undefined,
relationShip: formData.value.relationShip
})
formData.value.relativeId = tempId
formData.value.newRelativeName = ''
formData.value.birthday = undefined
formData.value.height = undefined
formData.value.relationShip = undefined
showRelativeSelect.value = true
showAddRelativeDialog.value = false
newRelativeForm.value.name = ''
ElMessage.success('家属已添加')
} catch (err) { }
}
// 重置/关闭/提交
const handleClose = () => {
emit('update:visible', false)
resetForm()
}
const handleVisibleChange = (value: boolean) => {
emit('update:visible', value)
if (!value) resetForm()
}
const resetForm = () => {
formData.value = {
personType: 1,
type: undefined,
employeeId: undefined,
newEmployeeName: '',
relatedEmployeeId: undefined,
newRelatedEmployeeName: '',
relativeId: undefined,
newRelativeName: '',
relationShip: undefined,
birthday: undefined,
weight: undefined,
height: undefined,
herbs: [],
diagnosisResult: '',
userMedicinaAdvice: '',
visitDate: ''
}
localCreatedPersons.value = []
showEmployeeSelect.value = true
showRelatedEmployeeSelect.value = true
showRelativeSelect.value = true
newEmployeeForm.value.name = ''
newRelatedEmployeeForm.value.name = ''
newRelativeForm.value.name = ''
showAddEmployeeDialog.value = false
showAddRelatedEmployeeDialog.value = false
showAddRelativeDialog.value = false
}
const handleSubmit = async () => {
if (!formRef.value || isViewMode.value) return
try {
await formRef.value.validate()
} catch (err) {
return
}
submitLoading.value = true
try {
const personObj: any = {}
const isPatientEmployee = formData.value.personType === 1
personObj.isEmployee = isPatientEmployee
personObj.type = isPatientEmployee ? formData.value.type : undefined
if (isPatientEmployee) {
if (formData.value.employeeId) {
if (typeof formData.value.employeeId === 'string' && String(formData.value.employeeId).startsWith('tmp_')) {
const tmpEmp = localCreatedPersons.value.find(p => p.tempId === formData.value.employeeId && p.isEmployee)
const name = tmpEmp ? tmpEmp.name : undefined
if (name) {
personObj.name = name
personObj.employeeIsTemp = true
personObj.employeeName = name
} else {
personObj.name = employeeOptions.value.find(e => e.id === formData.value.employeeId)?.name
personObj.employeeIsTemp = true
personObj.employeeName = personObj.name
}
} else {
personObj.id = Number(formData.value.employeeId)
personObj.name = employeeOptions.value.find(e => e.id === formData.value.employeeId)?.name
}
} else if (formData.value.newEmployeeName) {
personObj.name = formData.value.newEmployeeName.trim()
personObj.employeeIsTemp = true
personObj.employeeName = personObj.name
}
} else {
if (formData.value.relativeId) {
if (typeof formData.value.relativeId === 'string' && String(formData.value.relativeId).startsWith('tmp_')) {
const tmpRel = localCreatedPersons.value.find(p => p.tempId === formData.value.relativeId && !p.isEmployee)
personObj.name = tmpRel ? tmpRel.name : relativeOptions.value.find(r => r.id === formData.value.relativeId)?.name
personObj.tempId = formData.value.relativeId
} else {
personObj.id = Number(formData.value.relativeId)
personObj.name = relativeOptions.value.find(r => r.id === formData.value.relativeId)?.name
}
} else if (formData.value.newRelativeName) {
personObj.name = formData.value.newRelativeName.trim()
}
if (formData.value.relatedEmployeeId) {
if (typeof formData.value.relatedEmployeeId === 'string' && String(formData.value.relatedEmployeeId).startsWith('tmp_')) {
const tmpEmp = localCreatedPersons.value.find(p => p.tempId === formData.value.relatedEmployeeId && p.isEmployee)
const name = tmpEmp ? tmpEmp.name : employeeOptions.value.find(e => e.id === formData.value.relatedEmployeeId)?.name
if (name) {
personObj.employeeIsTemp = true
personObj.employeeName = name
} else {
personObj.employeeId = formData.value.relatedEmployeeId
}
} else {
personObj.employeeId = Number(formData.value.relatedEmployeeId)
}
} else if (formData.value.newRelatedEmployeeName) {
personObj.employeeIsTemp = true
personObj.employeeName = formData.value.newRelatedEmployeeName.trim()
}
personObj.relationShip = formData.value.relationShip
}
if ((!personObj.name || personObj.name === undefined) && personObj.id && typeof personObj.id === 'string' && String(personObj.id).startsWith('tmp_')) {
const tmp = localCreatedPersons.value.find(p => p.tempId === personObj.id)
if (tmp) personObj.name = tmp.name
}
personObj.birthday = formData.value.birthday
personObj.weight = formData.value.weight
personObj.height = formData.value.height
const herbsPayload = formData.value.herbs.map(h => ({
id: h.id || 0,
amountG: h.amountG,
note: h.note
}))
const payload: any = {
id: props.prescriptionId ?? undefined,
person: personObj,
diagnosisResult: formData.value.diagnosisResult,
userMedicinaAdvice: formData.value.userMedicinaAdvice,
visitDate: formData.value.visitDate,
herbs: herbsPayload
}
const res: any = await prescriptionApi.save(payload)
if (res && (res.code === 0 || res.data !== undefined)) {
ElMessage.success(props.isEdit ? '处方更新成功' : '处方保存成功')
emit('refresh-list')
handleClose()
} else {
ElMessage.success('保存成功')
emit('refresh-list')
handleClose()
}
} catch (err) {
console.error('保存处方失败', err)
} finally {
submitLoading.value = false
}
}
// 监听弹窗显示
watch(() => props.visible, async (newVal) => {
if (newVal) {
resetForm()
await Promise.all([
fetchEmployees(),
fetchRelationTypes(),
loadAllMedicinals()
])
// 编辑/查看模式:加载详情
if ((props.isEdit || isViewMode.value) && props.prescriptionId) {
try {
const res: any = await prescriptionApi.getDetail(props.prescriptionId)
const detail = res?.data ?? null
if (detail) {
const person = detail.person || {}
formData.value.personType = person.isEmployee ? 1 : 2
if (formData.value.personType === 1) {
formData.value.employeeId = person.id ?? undefined
formData.value.birthday = person.birthday ?? undefined
formData.value.height = person.height ?? undefined
formData.value.type = person.type ?? undefined
} else {
formData.value.relatedEmployeeId = person.employeeId ?? undefined
formData.value.relativeId = person.id ?? undefined
formData.value.birthday = person.birthday ?? undefined
formData.value.height = person.height ?? undefined
formData.value.relationShip = person.relationShip ?? undefined
if (formData.value.relatedEmployeeId) {
loadRelatives(formData.value.relatedEmployeeId)
}
}
formData.value.weight = person.weight ?? undefined
formData.value.diagnosisResult = detail.diagnosisResult ?? ''
formData.value.userMedicinaAdvice = detail.userMedicinaAdvice ?? ''
formData.value.visitDate = detail.visitDate ?? ''
formData.value.herbs = (detail.herbs || []).map((h: any) => ({
id: h.medicinalId,
medicinalName: h.medicinalName,
amountG: h.amountG,
note: h.note || ''
}))
}
} catch (err) {
}
}
}
})
</script>
<style lang="scss">
.modal-class {
max-height: 60vh !important;
overflow-y: auto !important;
}
</style>
<style scoped lang="scss">
.prescription-form {
.form-section {
margin-bottom: 20px;
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
}
}
.dialog-footer {
text-align: right;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.gap-2 {
gap: 8px;
}
.flex-1 {
flex: 1;
}
.ml-4 {
margin-left: 16px;
}
.w-full {
width: 100%;
}
.mt-2 {
margin-top: 8px;
}
</style>
@@ -1,375 +0,0 @@
<template>
<el-dialog v-model="visibleRef" title="处方报告" width="1500px" :body-class="'report-modal-body'" :show-close="false"
:top="'0'" append-to-body>
<!-- 报告核心内容 -->
<div class="report-content">
<!-- 3.1 就诊人基本信息 -->
<div class="report-section">
<h3 class="section-heading">一、就诊人基本信息</h3>
<div class="info-grid">
<div class="info-item">
<span class="info-label">就诊人姓名:</span>
<span class="info-value">{{ reportData.patientName || "暂无数据" }}</span>
</div>
<div class="info-item">
<span class="info-label">出生日期:</span>
<span class="info-value">{{ reportData.birthday || "暂无数据" }}</span>
</div>
<div class="info-item">
<span class="info-label">身高:</span>
<span class="info-value">{{ reportData.height ? `${reportData.height} cm` : "暂无数据" }}</span>
</div>
<div class="info-item">
<span class="info-label">体重:</span>
<span class="info-value">{{ reportData.weight ? `${reportData.weight} kg` : "暂无数据" }}</span>
</div>
<div class="info-item">
<span class="info-label">就诊时间:</span>
<span class="info-value">{{ reportData.visitDate || "暂无数据" }}</span>
</div>
</div>
</div>
<!-- 3.2 药材消耗明细(超紧凑卡片布局) -->
<div class="report-section mt-16">
<h3 class="section-heading">二、药材消耗明细</h3>
<div class="herbs-grid-container">
<!-- 单个药材卡片(更小尺寸) -->
<div class="herb-card" v-for="(herb, index) in reportData.herbs" :key="index">
<div class="herb-name">
<span class="herb-label">名称:</span>
{{ herb.medicinalName || "未命名药材" }}
</div>
<div class="herb-amount">
<span class="herb-label">用量:</span>
{{ herb.amountG !== undefined ? `${herb.amountG} g` : "0 g" }}
</div>
</div>
<!-- 无药材空状态 -->
<div v-if="reportData.herbs.length === 0" class="empty-herbs">
<el-icon size="24" color="#9ca3af" class="empty-icon">
<DocumentEmpty />
</el-icon>
<p class="empty-text">当前处方暂无药材消耗记录</p>
</div>
</div>
</div>
<!-- 3.3 诊断与用药建议 -->
<div class="report-section mt-16">
<h3 class="section-heading">三、诊断与用药建议</h3>
<div class="advice-container">
<div class="advice-item">
<span class="advice-label">诊断结果:</span>
<div class="advice-content">
{{ reportData.diagnosisResult || "暂无诊断结果描述" }}
</div>
</div>
<div class="advice-item mt-8">
<span class="advice-label">用药建议:</span>
<div class="advice-content">
{{ reportData.userMedicinaAdvice || "暂无用药建议描述" }}
</div>
</div>
</div>
</div>
</div>
<template #footer>
<el-button @click="handleClose">关闭报告</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { ElMessage } from "element-plus";
import { prescriptionApi } from "@/api";
// Props定义
interface Props {
modelValue: boolean;
prescriptionId?: number;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
prescriptionId: undefined,
});
// 弹窗显隐控制
const visibleRef = ref(props.modelValue);
const emit = defineEmits<{
"update:modelValue": [value: boolean];
}>();
// 监听父组件传值,同步弹窗状态
watch(
() => props.modelValue,
(newVal) => {
visibleRef.value = newVal;
},
{ immediate: true }
);
// 数据状态
const loading = ref(false);
const error = ref(false);
const errorMsg = ref("");
const reportData = ref({
patientName: "",
birthday: "",
height: undefined,
weight: undefined,
visitDate: "",
herbs: [] as Array<{ medicinalName: string; amountG: number; note: string }>,
diagnosisResult: "",
userMedicinaAdvice: "",
});
// 加载报告数据
const fetchReportData = async () => {
if (!props.prescriptionId) return;
try {
loading.value = true;
error.value = false;
const res: any = await prescriptionApi.getDetail(props.prescriptionId);
const detail = res?.data || {};
const person = detail.person || {};
reportData.value = {
patientName: person.name || "",
birthday: person.birthday || "",
height: person.height,
weight: person.weight,
visitDate: detail.visitDate || "",
herbs: (detail.herbs || []).map((h: any) => ({
medicinalName: h.medicinalName || "未命名药材",
amountG: h.amountG || 0,
note: h.note || "",
})),
diagnosisResult: detail.diagnosisResult || "",
userMedicinaAdvice: detail.userMedicinaAdvice || "",
};
} catch (err: any) {
error.value = true;
errorMsg.value = err.message || "加载报告失败,请检查网络";
ElMessage.error(errorMsg.value);
} finally {
loading.value = false;
}
};
// 关闭弹窗
const handleClose = () => {
emit("update:modelValue", false);
};
// 弹窗关闭前重置数据
const handleBeforeClose = () => {
reportData.value = {
patientName: "",
birthday: "",
height: undefined,
weight: undefined,
visitDate: "",
herbs: [],
diagnosisResult: "",
userMedicinaAdvice: "",
};
error.value = false;
errorMsg.value = "";
};
// 监听处方ID变化,重新加载数据
watch(
() => props.prescriptionId,
(newId) => {
if (props.modelValue && newId) {
fetchReportData();
}
}
);
</script>
<style lang="scss">
.report-modal-body {
// padding: 20px !important;
font-size: 14px;
color: #303133;
background: #fff;
overflow-y: auto !important;
max-height: 100vh !important;
}
</style>
<style lang="scss" scoped>
/* 报告通用样式 */
.report-content {
width: 100%;
}
.report-section {
padding: 15px;
border: 1px solid #e6e6e6;
border-radius: 8px;
margin-bottom: 16px;
background: #fff;
}
.section-heading {
font-size: 16px;
font-weight: 700;
color: #1f2937;
margin: 0 0 12px 0;
padding-bottom: 8px;
border-bottom: 2px solid #409eff;
}
/* 就诊人基本信息样式 */
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.info-item {
display: flex;
align-items: center;
line-height: 24px;
}
.info-label {
font-weight: 600;
color: #4b5563;
min-width: 100px;
}
.info-value {
flex: 1;
word-break: break-all;
color: #303133;
}
/* 药材消耗明细样式(超紧凑设计) */
.herbs-grid-container {
// display: grid;
// /* 核心:每列最小宽度缩小到150px,可根据需要进一步减小到120px */
// grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
// gap: 8px;
/* 卡片间距缩小 */
padding: 8px 0;
display: flex;
flex-wrap: wrap;
gap: 15px;
}
/* 单个药材卡片(更小尺寸) */
.herb-card {
padding: 8px;
/* 内边距大幅缩小 */
border: 1px solid #e6e6e6;
border-radius: 4px;
background: #f9fafb;
line-height: 1.3;
/* 行高压缩 */
font-size: 12px;
/* 字体缩小 */
min-width: 70px;
}
/* 卡片内标签样式 */
.herb-label {
font-weight: 600;
color: #4b5563;
font-size: 13px;
/* 标签字体更小 */
}
/* 药材空状态样式 */
.empty-herbs {
grid-column: 1 / -1;
text-align: center;
padding: 30px 0;
color: #9ca3af;
background: #f9fafb;
border-radius: 4px;
font-size: 14px;
}
.empty-icon {
margin-bottom: 8px;
}
/* 诊断与用药建议样式 */
.advice-container {
padding: 8px 0;
}
.advice-item {
display: flex;
flex-direction: column;
}
.advice-label {
font-weight: 600;
color: #4b5563;
margin-bottom: 4px;
}
.advice-content {
padding: 8px 12px;
border: 1px solid #e6e6e6;
border-radius: 4px;
min-height: 60px;
line-height: 1.5;
background: #f9fafb;
word-break: break-all;
color: #303133;
}
/* 间距工具类 */
.mt-8 {
margin-top: 8px;
}
.mt-16 {
margin-top: 16px;
}
/* 加载/错误样式 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
}
.loading-text {
margin-top: 16px;
color: #606266;
}
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
color: #f56c6c;
}
.error-icon {
margin-bottom: 12px;
}
.error-text {
margin-bottom: 16px;
font-size: 14px;
}
.retry-btn {
min-width: 100px;
}
</style>
@@ -1,150 +0,0 @@
<template>
<div class="table-box">
<!-- 原有:处方列表(仅操作列新增“查看报告”按钮) -->
<ProTable ref="proTable" title="处方列表" :columns="columns" :request-api="getPrescriptionListApi" :row-key="rowKey">
<!-- 原有:表格头部按钮(不变) -->
<template #tableHeader>
<s-button suffix="处方" type="success" @click="handleAddPrescription" />
<el-button type="primary" @click="handleViewTotalCost">查看处方总价</el-button>
</template>
<!-- 原有:就诊人类型自定义插槽(不变) -->
<template #personType="scope">
<el-space>
<div v-if="scope.row.personType === 1" class="flex items-center">
<span>公司员工</span>
<el-tag v-if="scope.row.type == 1" type="success" size="small" class="ml-2">领导</el-tag>
<el-tag v-if="scope.row.type == 2" type="info" size="small" class="ml-2">普通员工</el-tag>
</div>
<span v-else-if="scope.row.personType === 2">
员工家属,所属员工:{{ scope.row.employeeName || "未知" }}
</span>
<span v-else>未知类型</span>
</el-space>
</template>
<!-- 关键改动:操作列新增“查看报告”按钮 -->
<template #operation="{ row }">
<s-button link :opt="FormOptEnum.VIEW" size="small" @click="handleViewDetail(row.id)">
查看详情
</s-button>
<s-button v-if="row.canEdit" link :opt="FormOptEnum.EDIT" size="small" class="ml-2" @click="handleEdit(row.id)">
编辑
</s-button>
<!-- 新增:触发报告弹窗 -->
<s-button link :opt="FormOptEnum.VIEW" size="small" class="ml-2" @click="handleOpenReport(row.id)">
查看报告
</s-button>
</template>
</ProTable>
<!--处方详情/编辑弹窗 -->
<PrescriptionDetailDialog v-model:visible="showDetailDialog" :prescription-id="currentPrescriptionId"
:is-edit="isEdit" @refresh-list="handleRefresh" />
<!-- 报告弹窗组件 -->
<PrescriptionReportDialog v-model="showReportDialog" :prescription-id="currentReportId" />
<!-- 处方总价弹窗 -->
<el-dialog v-model="showTotalCostDialog" title="查看处方总价" width="400px" :close-on-click-modal="false">
<div style="padding: 20px; text-align: center;">
<div style="font-size: 16px; color: #606266; margin-bottom: 10px;">预估总药材成本</div>
<div style="color: #f56c6c; font-size: 32px; font-weight: bold;">
¥{{ totalCostData || 0 }}
</div>
</div>
<template #footer>
<el-button @click="showTotalCostDialog = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="PrescriptionModule">
import { ref } from "vue";
import ProTable from "@/components/ProTable/index.vue";
import type { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
import { prescriptionApi } from "@/api";
// 原有:引入详情弹窗组件(不变)
import PrescriptionDetailDialog from "./components/PrescriptionDetailDialog.vue";
// 关键改动:引入独立的报告弹窗组件
import PrescriptionReportDialog from "./components/PrescriptionReportDialog.vue";
import { FormOptEnum } from "@/enums";
// 原有:表格ref、详情弹窗状态(不变)
const proTable = ref<ProTableInstance | null>(null);
const showDetailDialog = ref(false);
const currentPrescriptionId = ref<number | undefined>(undefined);
const isEdit = ref(false);
// 关键改动:新增报告弹窗相关状态
const showReportDialog = ref(false); // 报告弹窗显隐
const currentReportId = ref<number | undefined>(undefined); // 当前报告对应的处方ID
// 新增:处方总价弹窗相关状态
const showTotalCostDialog = ref(false); // 总价弹窗显隐
const totalCostData = ref<number>(0); // 总价数据
// 原有:就诊人类型选项、表格列配置(不变)
const personTypeOptions = [
{ label: "公司员工", value: 1 },
{ label: "员工家属", value: 2 }
];
const columns: ColumnProps<any>[] = [
{ type: "selection", fixed: "left", width: 80 },
{ prop: "personName", label: "就诊人", search: { el: "input" } },
{
prop: "personType",
label: "就诊人类型",
sortable: false,
enum: personTypeOptions,
search: { el: "select" }
},
{ prop: "visitDate", label: "就诊时间" },
{ prop: "createTime", label: "添加时间" },
{ prop: "totalMedicineCost", label: "预估药材成本" },
{ prop: "operation", label: "操作", width: 280, fixed: "right" }
];
const rowKey = "id";
const getPrescriptionListApi = (params: any) => {
return prescriptionApi.getList(params);
};
//新增、查看详情、编辑、刷新
const handleAddPrescription = () => {
currentPrescriptionId.value = undefined;
isEdit.value = false;
showDetailDialog.value = true;
};
const handleViewDetail = (id: number) => {
currentPrescriptionId.value = id;
isEdit.value = false;
showDetailDialog.value = true;
};
const handleEdit = (id: number) => {
currentPrescriptionId.value = id;
isEdit.value = true;
showDetailDialog.value = true;
};
const handleRefresh = () => {
proTable.value?.refresh?.();
};
// 打开报告弹窗
const handleOpenReport = (id: number) => {
currentReportId.value = id; // 传递当前处方ID给报告组件
showReportDialog.value = true; // 显示报告弹窗
};
// 新增:查看处方总价
const handleViewTotalCost = async () => {
showTotalCostDialog.value = true;
try {
const { data } = await prescriptionApi.getTotalCost();
totalCostData.value = data || 0;
} catch (error) {
console.error("获取处方总价失败:", error);
totalCostData.value = 0;
}
};
</script>
File diff suppressed because it is too large Load Diff