diff --git a/pages/case/archive-detail.vue b/pages/case/archive-detail.vue
index e033313..cf84f4c 100644
--- a/pages/case/archive-detail.vue
+++ b/pages/case/archive-detail.vue
@@ -118,6 +118,12 @@
:reachBottomTime="reachBottomTime"
:floatingBottom="floatingBottom"
/>
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
+
+ 总数
+ {{ item.usageCount }}
+
+
+ 剩余数
+ {{ item.restUsageCount }}
+
+
+ 有效期
+ {{ item.validTimeText }}
+
+
+
+ 购买日期
+ {{ item.purchaseDate }}
+
+
+ 治疗科室
+ {{ item.treatmentDeptName || '--' }}
+
+
+ 录单人
+ {{ item.creatorName || item.billdCreator || '--' }}
+
+
+
+
+
+ 治疗日期
+ {{ item.treatmentDate }}
+
+
+ 治疗数
+ {{ item.deductUsageCount }}
+
+
+ 治疗部位
+ {{ item.treatmentArea || '--' }}
+
+
+ 治疗科室
+ {{ item.treatmentDeptName || '--' }}
+
+
+ 治疗医生
+ {{ item.doctorName || item.treatmentDoctorUserId || '--' }}
+
+
+
+
+
+
+ 加载中...
+ 暂无{{ currentType === 'treatment' ? '权益记录' : '治疗明细' }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/message/benefit-entry.vue b/pages/message/benefit-entry.vue
index 20cc06c..7e0a9ce 100644
--- a/pages/message/benefit-entry.vue
+++ b/pages/message/benefit-entry.vue
@@ -22,6 +22,71 @@
权益项目
+
+
+
+ 套餐名称
+ {{ entry.packageName }}
+
+ ¥{{ Number(entry.price || 0).toFixed(2) }}
+
+
+ 组合形式:{{ entry.packageType === "free" ? `${entry.projects.length}选${entry.projectNumber}` : "固定组合" }}
+ 必选项:{{ entry.requiredCount }}
+ 可选项:{{ entry.optionalSelectedCount }} / {{ entry.optionalCount }}
+
+
+
+ 数量
+
+
+
+ 折扣
+
+
+
+ 总价
+ ¥{{ Number(entry.totalPrice || 0).toFixed(2) }}
+
+
+
+
+ 数量
+
+
+
+
+
+
+ {{ projectEntry.project.projectName }}
+
+ ¥{{ Number(projectEntry.price || 0).toFixed(2) }} × {{ projectEntry.usageCount }},折扣 {{ Number(projectEntry.discount || 0).toFixed(1) }}
+
+ 治疗科室
+
+ {{ projectEntry.dept?.deptName || "请选择治疗科室" }}
+
+
+
+ 计划治疗医生
+
+ {{ projectEntry.treatmentDoctorName || "选择治疗医生" }}
+
+
+
+ 有效期
+ {{ projectEntry.validTime }}
+
+
+ 删除套餐
+
+
项目名称
@@ -138,6 +203,7 @@
删除此项目
+
@@ -148,15 +214,16 @@
-
+
- 单项目
+
{{ project.projectName }}
@@ -164,7 +231,12 @@
¥{{ Number(project.price || 0).toFixed(2) }}
- 暂无可用权益项目
+ 套餐
+
+ {{ packageItem.name }}{{ packageItem.packageType === "free" ? `自由组合:${packageItem.projects.length}选${packageItem.projectNumber}` : "固定组合" }} · {{ (packageItem.projects || []).map((item) => item.projectName).join("、") }}
+ ¥{{ Number(packageItem.packagePrice || 0).toFixed(2) }}
+
+ 暂无可用权益项目或套餐
加载中...
已加载全部权益项目
@@ -206,8 +278,10 @@ const projectPickerVisible = ref(false);
const projectPickerIndex = ref(-1);
const projectKeyword = ref("");
const projectList = ref([]);
+const packageList = ref([]);
const projectLoading = ref(false);
const projectHasMore = ref(true);
+const packageHasMore = ref(true);
const projectPage = ref(1);
const projectPageSize = 30;
const VALID_YEAR_SPAN = 50;
@@ -413,31 +487,47 @@ function normalizeProject(project = {}) {
async function loadProjects({ reset = false } = {}) {
if (projectLoading.value) return;
- if (!reset && !projectHasMore.value) return;
+ if (!reset && !projectHasMore.value && !packageHasMore.value) return;
const page = reset ? 1 : projectPage.value;
projectLoading.value = true;
if (reset) {
projectList.value = [];
+ packageList.value = [];
projectHasMore.value = true;
+ packageHasMore.value = true;
}
try {
- const res = await api("getProjectList", {
+ const [res, packageRes] = await Promise.all([api("getProjectList", {
corpId: corpId.value,
page,
pageSize: projectPageSize,
projectName: projectKeyword.value.trim(),
projectStatus: "enable",
showDepts: true,
- }, false);
+ }, false), api("getProjectPackageList", {
+ corpId: corpId.value,
+ page,
+ pageSize: projectPageSize,
+ name: projectKeyword.value.trim(),
+ enable: true,
+ }, false)]);
const list = res?.data?.list || res?.list || res?.data || [];
const nextList = Array.isArray(list) ? list.map(normalizeProject) : [];
projectList.value = reset ? nextList : projectList.value.concat(nextList);
+ const packages = packageRes?.data?.list || packageRes?.list || packageRes?.data || [];
+ const nextPackages = Array.isArray(packages) ? packages.filter((item) => Array.isArray(item.projects) && item.projects.length) : [];
+ packageList.value = reset ? nextPackages : packageList.value.concat(nextPackages);
projectPage.value = page + 1;
projectHasMore.value = nextList.length >= projectPageSize;
+ packageHasMore.value = nextPackages.length >= projectPageSize;
} catch (error) {
- if (reset) projectList.value = [];
+ if (reset) {
+ projectList.value = [];
+ packageList.value = [];
+ }
projectHasMore.value = false;
+ packageHasMore.value = false;
uni.showToast({ title: "项目加载失败", icon: "none" });
} finally {
projectLoading.value = false;
@@ -525,6 +615,110 @@ function selectProject(project) {
closeProjectPicker();
}
+function createPackageProjectEntry(project = {}, packageItem = {}) {
+ const normalizedProject = normalizeProject(project);
+ const hasValidDays = packageItem.hasValidDays === true;
+ const validDays = Number(packageItem.validDays || 0);
+ const validDate = hasValidDays && validDays > 0
+ ? formatDate(new Date(Date.now() + validDays * 24 * 60 * 60 * 1000))
+ : "";
+ return {
+ ...createEntry(),
+ project: normalizedProject,
+ price: Number(normalizedProject.price ?? project.price ?? 0),
+ usageCount: Number(project.number || 1),
+ discount: packageItem.packageType === "free" ? 10 : Number(project.discount || 10),
+ totalPrice: Number(project.totalPrice || 0),
+ selected: packageItem.packageType === "fixed" || project.required === true,
+ required: project.required === true,
+ hasValidDays,
+ validDays,
+ validTime: validDate,
+ dept: normalizedProject.depts?.length === 1 ? normalizedProject.depts[0] : null,
+ };
+}
+
+async function selectPackage(packageItem) {
+ const targetIndex = projectPickerIndex.value;
+ if (targetIndex < 0) return;
+ try {
+ uni.showLoading({ title: "加载套餐项目" });
+ const ids = packageItem.projects.map((item) => item._id).filter(Boolean);
+ const res = await api("getProjectList", { corpId: corpId.value, ids, page: 1, pageSize: ids.length, projectStatus: "enable", showDepts: true }, false);
+ const fullProjects = res?.data?.list || res?.list || res?.data || [];
+ const projects = packageItem.projects.map((item) => {
+ const fullProject = Array.isArray(fullProjects) ? fullProjects.find((project) => project._id === item._id) : null;
+ return createPackageProjectEntry({ ...(fullProject || {}), ...item }, packageItem);
+ });
+ const requiredCount = projects.filter((item) => item.required).length;
+ const entry = {
+ ...createEntry(),
+ isPackage: true,
+ packageId: packageItem._id,
+ packageName: packageItem.name,
+ packageType: packageItem.packageType,
+ packagePrice: Number(packageItem.packagePrice || 0),
+ price: Number(packageItem.packagePrice || 0),
+ usageCount: 1,
+ discount: 10,
+ totalPrice: Number(packageItem.packagePrice || 0),
+ projectNumber: Number(packageItem.projectNumber || projects.length),
+ requiredCount,
+ optionalCount: Math.max(Number(packageItem.projectNumber || projects.length) - requiredCount, 0),
+ projects,
+ };
+ syncPackageSelection(entry);
+ syncPackageTotals(entry);
+ entries.value.splice(targetIndex, 1, entry);
+ closeProjectPicker();
+ } catch (error) {
+ uni.showToast({ title: "套餐加载失败", icon: "none" });
+ } finally {
+ uni.hideLoading();
+ }
+}
+
+function syncPackageSelection(entry) {
+ entry.selectedCount = entry.projects.filter((item) => item.selected).length;
+ entry.optionalSelectedCount = Math.max(entry.selectedCount - entry.requiredCount, 0);
+}
+
+function isPackageSelectionFull(entry) {
+ return Number(entry.selectedCount || 0) >= Number(entry.projectNumber || 0);
+}
+
+function togglePackageProject(entry, projectEntry) {
+ if (projectEntry.required) return;
+ if (!projectEntry.selected && isPackageSelectionFull(entry)) {
+ uni.showToast({ title: "已达到可选项目上限", icon: "none" });
+ return;
+ }
+ projectEntry.selected = !projectEntry.selected;
+ syncPackageSelection(entry);
+ syncPackageTotals(entry);
+}
+
+function calculatePackageTotal(entry) {
+ entry.usageCount = Math.max(1, Number(entry.usageCount || 1));
+ entry.discount = clampNumber(toNumber(entry.discount, 10), 0, 10);
+ entry.totalPrice = toFixedNumber(Number(entry.price || 0) * entry.usageCount * entry.discount / 10, 2);
+ syncPackageTotals(entry);
+}
+
+function syncPackageTotals(entry) {
+ const selected = entry.projects.filter((item) => item.selected);
+ const baseTotal = selected.reduce((sum, item) => sum + Number(item.price || 0) * Number(item.usageCount || 1), 0);
+ let allocated = 0;
+ selected.forEach((item, index) => {
+ const total = index === selected.length - 1
+ ? Number(entry.totalPrice || 0) - allocated
+ : (baseTotal ? Number(entry.totalPrice || 0) * (Number(item.price || 0) * Number(item.usageCount || 1)) / baseTotal : 0);
+ item.totalPrice = toFixedNumber(Math.max(total, 0), 2);
+ item.discount = item.price && item.usageCount ? toFixedNumber(item.totalPrice / (item.price * item.usageCount) * 10, 1) : 0;
+ allocated += item.totalPrice;
+ });
+}
+
function changeDept(event, entry) {
entry.dept = getProjectDepts(entry)[Number(event.detail.value)] || null;
entry.treatmentDoctorUserId = "";
@@ -576,9 +770,33 @@ function goBack() {
uni.navigateBack();
}
+function goBackToConversation() {
+ if (!groupId.value) {
+ goBack();
+ return;
+ }
+ const conversationID = `GROUP${groupId.value}`;
+ uni.redirectTo({
+ url: `/pages/message/index?conversationID=${encodeURIComponent(conversationID)}&groupID=${encodeURIComponent(groupId.value)}&fromCase=true`,
+ });
+}
+
function validateEntries() {
for (let index = 0; index < entries.value.length; index += 1) {
const entry = entries.value[index];
+ if (entry.isPackage) {
+ if (!Number(entry.usageCount) || Number(entry.usageCount) <= 0) return `${entry.packageName}数量必须大于0`;
+ if (!Number.isFinite(Number(entry.discount)) || Number(entry.discount) < 0 || Number(entry.discount) > 10) return `${entry.packageName}折扣需在0到10之间`;
+ if (entry.packageType === "free" && entry.selectedCount !== entry.projectNumber) {
+ return `${entry.packageName}需要选择${entry.projectNumber}项,当前已选择${entry.selectedCount}项`;
+ }
+ const selectedProjects = entry.projects.filter((item) => item.selected);
+ for (const projectEntry of selectedProjects) {
+ if (!projectEntry.dept?._id) return `${projectEntry.project.projectName}请选择治疗科室`;
+ if (projectEntry.hasValidDays && !projectEntry.validTime) return `${projectEntry.project.projectName}请选择有效日期`;
+ }
+ continue;
+ }
const label = `第${index + 1}个权益项目`;
if (!entry.project?._id) return `${label}请选择项目`;
if (Number(entry.price) < 0 || Number.isNaN(Number(entry.price))) return `${label}价格不能小于0`;
@@ -643,7 +861,17 @@ async function submitEntries() {
const userId = accountStore.doctorInfo?.userid || accountStore.account?.userid || "";
try {
const createdEntries = [];
- for (const entry of entries.value) {
+ const submitEntries = entries.value.flatMap((entry) => {
+ if (!entry.isPackage) return [entry];
+ return entry.projects.filter((item) => item.selected).map((item) => ({
+ ...item,
+ packageId: entry.packageId,
+ packageName: entry.packageName,
+ packageType: entry.packageType,
+ packagePrice: Number(entry.packagePrice || 0),
+ }));
+ });
+ for (const entry of submitEntries) {
const count = Number(entry.usageCount);
const price = Number(entry.price);
const discount = Number(entry.discount);
@@ -670,16 +898,22 @@ async function submitEntries() {
createTreatementTime: Date.now(),
billTime: Date.now(),
validTime: entry.validTime ? new Date(`${entry.validTime} 23:59:59`).getTime() : null,
+ hasValidDays: Boolean(entry.hasValidDays),
+ validDays: Number(entry.validDays || 0),
+ packageId: entry.packageId || "",
+ packageName: entry.packageName || "",
+ packageType: entry.packageType || "",
+ packagePrice: Number(entry.packagePrice || 0),
teamId: teamId.value,
corpId: corpId.value,
};
- const res = await api("addTreatmentRecord", { params: treatmentData }, false);
+ const res = await api("addTreatmentRecord", { userId, operationSource: "wxApp", params: treatmentData }, false);
if (!res?.success) throw new Error(res?.message || "权益录入失败");
createdEntries.push(entry);
}
queueBenefitMessage(createdEntries);
uni.showToast({ title: "权益录入成功", icon: "success" });
- setTimeout(goBack, 500);
+ setTimeout(goBackToConversation, 500);
} catch (error) {
uni.showToast({ title: error.message || "权益录入失败", icon: "none" });
} finally {
@@ -817,6 +1051,75 @@ async function submitEntries() {
box-shadow: 0 10rpx 28rpx rgba(15, 23, 42, 0.05);
}
+.package-card-head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 20rpx;
+}
+
+.package-name {
+ color: #111827;
+ font-size: 30rpx;
+ font-weight: 600;
+ line-height: 1.4;
+}
+
+.package-price {
+ flex-shrink: 0;
+ color: #e85c2f;
+ font-size: 30rpx;
+ font-weight: 700;
+}
+
+.package-summary {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10rpx 20rpx;
+ margin-top: 18rpx;
+ padding: 16rpx;
+ border-radius: 12rpx;
+ background: #f5f7fa;
+ color: #606266;
+ font-size: 24rpx;
+}
+
+.package-project-card {
+ margin-top: 20rpx;
+ padding: 20rpx;
+ border: 1rpx solid #e5e7eb;
+ border-radius: 14rpx;
+ background: #fafbfc;
+}
+
+.package-project-title {
+ display: flex;
+ align-items: center;
+ gap: 10rpx;
+ color: #1f2937;
+ font-size: 28rpx;
+ font-weight: 600;
+}
+
+.package-project-detail {
+ margin-top: 10rpx;
+ color: #6b7280;
+ font-size: 24rpx;
+}
+
+.readonly-input {
+ display: flex;
+ align-items: center;
+ height: 76rpx;
+ box-sizing: border-box;
+ padding: 0 20rpx;
+ border-radius: 12rpx;
+ background: #f3f4f6;
+ color: #606266;
+ font-size: 28rpx;
+ font-weight: 600;
+}
+
.field-label {
margin-bottom: 12rpx;
color: #374151;
@@ -1068,6 +1371,13 @@ async function submitEntries() {
max-height: 55vh;
}
+.picker-group-title {
+ padding: 18rpx 8rpx 8rpx;
+ color: #374151;
+ font-size: 26rpx;
+ font-weight: 700;
+}
+
.project-item {
display: flex;
justify-content: space-between;
diff --git a/pages/message/components/chat-input.vue b/pages/message/components/chat-input.vue
index 67249d0..092d5b3 100644
--- a/pages/message/components/chat-input.vue
+++ b/pages/message/components/chat-input.vue
@@ -90,6 +90,7 @@ const props = defineProps({
corpId: { type: String, default: "" },
orderStatus: { type: String, default: "" },
isGenerating: { type: Boolean, default: false },
+ canBenefitEntry: { type: Boolean, default: false },
});
// Emits
@@ -481,11 +482,6 @@ const morePanelButtons = computed(() => {
icon: "/static/icon/xuanjiaowenzhang.png",
action: goToArticleList,
},
- {
- text: "权益录入",
- icon: "/static/icon/quanyi.svg",
- action: goToBenefitEntry,
- },
{
text: "问卷",
icon: "/static/icon/wenjuan.png",
@@ -493,6 +489,14 @@ const morePanelButtons = computed(() => {
},
];
+ if (props.canBenefitEntry) {
+ buttons.splice(4, 0, {
+ text: "权益录入",
+ icon: "/static/icon/quanyi.svg",
+ action: goToBenefitEntry,
+ });
+ }
+
// 根据订单状态显示不同的按钮
if (props.orderStatus === "finished") {
// 已结束状态:显示"开启会话"按钮
diff --git a/pages/message/index.vue b/pages/message/index.vue
index ea49ec0..4e416ba 100644
--- a/pages/message/index.vue
+++ b/pages/message/index.vue
@@ -157,6 +157,7 @@
:patientInfo="patientInfo"
:orderStatus="orderStatus"
:isGenerating="isGenerating"
+ :canBenefitEntry="doctorInfo?.benefitEntryEnabled === true"
@scrollToBottom="() => scrollToBottom(true)"
@messageSent="() => scrollToBottom(true)"
@endConsult="handleEndConsult"
diff --git a/utils/api.js b/utils/api.js
index 3daa57c..f792b38 100644
--- a/utils/api.js
+++ b/utils/api.js
@@ -94,6 +94,9 @@ const urlsConfig = {
// 健康档案相关接口
addMedicalRecord: 'addMedicalRecord',
addTreatmentRecord: "addTreatmentRecord",
+ getProjectPackageList: "getProjectPackageList",
+ getTreatmentRecord: "getTreatmentRecord",
+ getDeductRecord: "getDeductRecord",
getMedicalRecordById: 'getMedicalRecordById',
updateMedicalRecord: 'updateMedicalRecord',
removeMedicalRecord: 'removeMedicalRecord',