bug修复

This commit is contained in:
zhanchao 2026-09-10 14:57:14 +08:00
parent 22b2881141
commit 7d9f8d6522

View File

@ -1,5 +1,6 @@
<template>
<page-meta :page-style="projectPickerVisible || todoExecutorPickerVisible ? 'overflow: hidden;' : ''"></page-meta>
<page-meta
:page-style="projectPickerVisible || todoExecutorPickerVisible || billDeptPickerVisible ? 'overflow: hidden;' : ''"></page-meta>
<full-page ref="pageRef" pageStyle="background:#f5f6f8" pageClass="benefit-page-shell">
<view class="benefit-page">
<view class="patient-card">
@ -23,28 +24,18 @@
<view class="field-group">
<view class="field-label">开单科室</view>
<view class="bill-dept-picker">
<uni-data-picker
v-model="billDeptValue"
:localdata="billDeptOptions"
popup-title="请选择开单科室"
placeholder="请选择开单科室"
:clear-icon="true"
@change="changeBillDept"
/>
<view class="project-input" @tap="openBillDeptPicker">
<text :class="billDeptId ? 'value-text' : 'placeholder-text'">{{ billDeptName || "请选择开单科室" }}</text>
<uni-icons type="right" size="18" color="#999" />
</view>
</view>
<view class="field-group">
<view class="field-label">开单医生</view>
<picker
:range="getBillDoctors()"
range-key="anotherName"
:disabled="!billDeptId || !getBillDoctors().length"
@change="changeBillDoctor"
>
<picker :range="getBillDoctors()" range-key="anotherName" :disabled="!billDeptId || !getBillDoctors().length"
@change="changeBillDoctor">
<view class="project-input">
<text :class="billDoctorUserId ? 'value-text' : 'placeholder-text'">
{{ billDoctorName || (billDeptId ? "请选择开单医生" : "请先选择开单科室") }}
{{ billDoctorName || "请选择医生" }}
</text>
<uni-icons type="right" size="18" color="#999" />
</view>
@ -62,18 +53,23 @@
<view class="package-price">¥{{ Number(entry.price || 0).toFixed(2) }}</view>
</view>
<view class="package-summary">
<text>组合形式:{{ entry.packageType === "free" ? `${entry.projects.length}选${entry.projectNumber}` : "固定组合" }}</text>
<text>组合形式:{{ entry.packageType === "free" ? `${entry.projects.length}选${entry.projectNumber}` : "固定组合"
}}</text>
<text>必选项:{{ entry.requiredCount }}</text>
<text v-if="entry.packageType === 'free'">可选项:{{ entry.optionalSelectedCount }} / {{ entry.optionalCount }}</text>
<text v-if="entry.packageType === 'free'">可选项:{{ entry.optionalSelectedCount }} / {{ entry.optionalCount
}}</text>
</view>
<view v-if="showBenefitAmount" class="field-row">
<view class="field-block">
<view class="field-label">数量</view>
<input class="number-input" type="number" v-model="entry.usageCount" @input="calculatePackageTotal(entry)" />
<input class="number-input" type="number" v-model="entry.usageCount"
@input="calculatePackageTotal(entry)" />
</view>
<view class="field-block">
<view class="field-label">折扣</view>
<picker class="discount-picker" :class="{ 'discount-disabled': !entry.enableDiscount }" mode="multiSelector" :range="getDiscountPickerRange(entry)" :value="getDiscountPickerValue(entry)" :disabled="!entry.enableDiscount" @change="changeDiscount($event, entry)">
<picker class="discount-picker" :class="{ 'discount-disabled': !entry.enableDiscount }"
mode="multiSelector" :range="getDiscountPickerRange(entry)" :value="getDiscountPickerValue(entry)"
:disabled="!entry.enableDiscount" @change="changeDiscount($event, entry)">
<view class="number-input picker-value value-text">{{ Number(entry.discount).toFixed(1) }}</view>
</picker>
</view>
@ -85,49 +81,50 @@
<view v-else class="field-row">
<view class="field-block">
<view class="field-label">数量</view>
<input class="number-input" type="number" v-model="entry.usageCount" @input="calculatePackageTotal(entry)" />
<input class="number-input" type="number" v-model="entry.usageCount"
@input="calculatePackageTotal(entry)" />
</view>
</view>
<view v-for="projectEntry in entry.projects" :key="projectEntry.key" class="package-project-card" :class="{ 'required-project-card': projectEntry.required }">
<view v-for="projectEntry in entry.projects" :key="projectEntry.key" class="package-project-card"
:class="{ 'required-project-card': projectEntry.required }">
<view class="package-project-title">
<view v-if="entry.packageType === 'free' && projectEntry.required" class="required-checkbox">
<checkbox
:checked="projectEntry.selected"
disabled
color="#0877f1"
/>
<checkbox :checked="projectEntry.selected" disabled color="#0877f1" />
</view>
<checkbox
v-else-if="entry.packageType === 'free'"
:checked="projectEntry.selected"
:disabled="!projectEntry.selected && isPackageSelectionFull(entry)"
color="#0877f1"
@click="togglePackageProject(entry, projectEntry)"
/>
<checkbox v-else-if="entry.packageType === 'free'" :checked="projectEntry.selected"
:disabled="!projectEntry.selected && isPackageSelectionFull(entry)" color="#0877f1"
@click="togglePackageProject(entry, projectEntry)" />
<text v-if="projectEntry.required" class="required-badge">必选</text>
<text>{{ projectEntry.project.projectName }}</text>
</view>
<view class="package-project-detail">¥{{ Number(projectEntry.price || 0).toFixed(2) }} × {{ projectEntry.usageCount }},折扣 {{ Number(projectEntry.discount || 0).toFixed(1) }}</view>
<view class="package-project-detail">¥{{ Number(projectEntry.price || 0).toFixed(2) }} × {{
projectEntry.usageCount }},折扣 {{ Number(projectEntry.discount || 0).toFixed(1) }}</view>
<view v-if="projectEntry.selected" class="field-group">
<view class="field-label">治疗科室</view>
<picker :range="getProjectDepts(projectEntry)" range-key="deptName" :disabled="!getProjectDepts(projectEntry).length" @change="changeDept($event, projectEntry)">
<view class="project-input" :class="{ 'required-select': !projectEntry.dept }"><text :class="projectEntry.dept ? 'value-text' : 'placeholder-text'">{{ projectEntry.dept?.deptName || "请选择治疗科室" }}</text><uni-icons type="right" size="18" color="#999" /></view>
<picker :range="getProjectDepts(projectEntry)" range-key="deptName"
:disabled="!getProjectDepts(projectEntry).length" @change="changeDept($event, projectEntry)">
<view class="project-input" :class="{ 'required-select': !projectEntry.dept }"><text
:class="projectEntry.dept ? 'value-text' : 'placeholder-text'">{{ projectEntry.dept?.deptName ||
"请选择治疗科室" }}</text><uni-icons type="right" size="18" color="#999" /></view>
</picker>
</view>
<view v-if="projectEntry.selected" class="field-group">
<view class="field-label">计划治疗医生</view>
<picker :range="getTreatmentDoctors(projectEntry)" range-key="anotherName" :disabled="!projectEntry.dept?._id || !getTreatmentDoctors(projectEntry).length" @change="changeTreatmentDoctor($event, projectEntry)">
<view class="project-input"><text :class="projectEntry.treatmentDoctorUserId ? 'value-text' : 'placeholder-text'">{{ projectEntry.treatmentDoctorName || "选择治疗医生" }}</text><uni-icons type="right" size="18" color="#999" /></view>
<picker :range="getTreatmentDoctors(projectEntry)" range-key="anotherName"
:disabled="!projectEntry.dept?._id || !getTreatmentDoctors(projectEntry).length"
@change="changeTreatmentDoctor($event, projectEntry)">
<view class="project-input"><text
:class="projectEntry.treatmentDoctorUserId ? 'value-text' : 'placeholder-text'">{{
projectEntry.treatmentDoctorName || "选择治疗医生" }}</text><uni-icons type="right" size="18"
color="#999" /></view>
</picker>
</view>
<view v-if="projectEntry.selected && Number(projectEntry.validDays || 0) > 0" class="field-group">
<view class="field-label">有效期</view>
<view class="valid-time-wrap">
<view class="valid-time-picker disabled-picker">
<picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex"
disabled
@columnchange="onValidTimeColumnChange"
@change="changeValidTime($event, projectEntry)">
<picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex" disabled
@columnchange="onValidTimeColumnChange" @change="changeValidTime($event, projectEntry)">
<view class="picker-value" :class="projectEntry.validTime ? 'value-text' : 'placeholder-text'">
{{ projectEntry.validTime || "选择日期/长期" }}
</view>
@ -142,144 +139,189 @@
</view>
<view v-if="projectEntry.selected && projectEntry.todoEnabled" class="todo-config-card">
<input v-model="projectEntry.todoConfig.taskContent" class="todo-input" placeholder="任务内容" />
<picker :range="todoTypeOptions" range-key="label" @change="changeTodoType($event, projectEntry)"><view class="project-input"><text>类型:{{ getTodoTypeLabel(projectEntry.todoConfig.eventType) }}</text><uni-icons type="right" size="16" color="#999" /></view></picker>
<view class="field-row"><view class="field-block"><view class="field-label">回访日期</view><view class="valid-time-picker" @tap="prepareTodoDatePicker(projectEntry, 'planDate')"><picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex" @columnchange="onValidTimeColumnChange" @change="changeTodoDate($event)"><view class="picker-value value-text">{{ projectEntry.todoConfig.planDate }}</view></picker></view></view><view class="field-block"><view class="field-label">截止日期</view><view class="valid-time-picker" @tap="prepareTodoDatePicker(projectEntry, 'expireDate')"><picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex" @columnchange="onValidTimeColumnChange" @change="changeTodoDate($event)"><view class="picker-value value-text">{{ projectEntry.todoConfig.expireDate }}</view></picker></view></view></view>
<view class="project-input" :class="{ 'required-select': !projectEntry.todoConfig.executorUserId }" @tap="openTodoExecutorPicker(projectEntry)"><text :class="projectEntry.todoConfig.executorUserId ? 'value-text' : 'placeholder-text'">{{ projectEntry.todoConfig.executorName || '请选择团队和处理人' }}</text><uni-icons type="right" size="16" color="#999" /></view>
<view class="todo-deduct-row"><text>完成关联核销权益</text><switch :checked="projectEntry.todoConfig.deductBenefit" color="#0877f1" @change="projectEntry.todoConfig.deductBenefit = $event.detail.value" /></view>
<picker :range="todoTypeOptions" range-key="label" @change="changeTodoType($event, projectEntry)">
<view class="project-input"><text>类型:{{ getTodoTypeLabel(projectEntry.todoConfig.eventType)
}}</text><uni-icons type="right" size="16" color="#999" /></view>
</picker>
<view class="field-row">
<view class="field-block">
<view class="field-label">回访日期</view>
<view class="valid-time-picker" @tap="prepareTodoDatePicker(projectEntry, 'planDate')">
<picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex"
@columnchange="onValidTimeColumnChange" @change="changeTodoDate($event)">
<view class="picker-value value-text">{{ projectEntry.todoConfig.planDate }}</view>
</picker>
</view>
</view>
<view class="field-block">
<view class="field-label">截止日期</view>
<view class="valid-time-picker" @tap="prepareTodoDatePicker(projectEntry, 'expireDate')">
<picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex"
@columnchange="onValidTimeColumnChange" @change="changeTodoDate($event)">
<view class="picker-value value-text">{{ projectEntry.todoConfig.expireDate }}</view>
</picker>
</view>
</view>
</view>
<view class="project-input" :class="{ 'required-select': !projectEntry.todoConfig.executorUserId }"
@tap="openTodoExecutorPicker(projectEntry)"><text
:class="projectEntry.todoConfig.executorUserId ? 'value-text' : 'placeholder-text'">{{
projectEntry.todoConfig.executorName || '请选择团队和处理人' }}</text><uni-icons type="right" size="16"
color="#999" /></view>
<view class="todo-deduct-row"><text>完成关联核销权益</text>
<switch :checked="projectEntry.todoConfig.deductBenefit" color="#0877f1"
@change="projectEntry.todoConfig.deductBenefit = $event.detail.value" />
</view>
</view>
</view>
<view class="remove-entry" @click="removeEntry(index)">删除套餐</view>
</template>
<template v-else>
<view class="field-label">项目名称</view>
<view class="project-input required-select" @click="openProjectPicker(index)">
<text :class="entry.project ? 'value-text' : 'placeholder-text'">
{{ entry.project?.projectName || "请选择权益项目" }}
</text>
<uni-icons type="right" size="18" color="#999" />
</view>
<view class="field-label">项目名称</view>
<view class="project-input required-select" @click="openProjectPicker(index)">
<text :class="entry.project ? 'value-text' : 'placeholder-text'">
{{ entry.project?.projectName || "请选择权益项目" }}
</text>
<uni-icons type="right" size="18" color="#999" />
</view>
<view v-if="showBenefitAmount" class="field-row">
<view class="field-block">
<view class="field-label">单价</view>
<input class="number-input" type="digit" v-model="entry.price" @input="calculateDiscount(entry)" />
</view>
<view class="field-block">
<view class="field-label">数量</view>
<input class="number-input" type="number" v-model="entry.usageCount" @input="calculateTotalPrice(entry)" />
</view>
</view>
<view v-if="showBenefitAmount" class="field-row">
<view class="field-block">
<view class="field-label">折扣</view>
<view class="discount-input-wrap">
<picker class="discount-picker" :class="{ 'discount-disabled': entry.isFree || !entry.isDiscount }" mode="multiSelector" :range="getDiscountPickerRange(entry)" :value="getDiscountPickerValue(entry)" :disabled="entry.isFree || !entry.isDiscount" @change="changeDiscount($event, entry)">
<view class="number-input picker-value value-text">{{ Number(entry.discount).toFixed(1) }}</view>
</picker>
<checkbox-group v-if="entry.isGift" class="free-check" @change="handleFreeChange($event, entry)">
<label class="free-check-label">
<checkbox value="free" :checked="entry.isFree" color="#0877f1" />
<text>赠送</text>
</label>
</checkbox-group>
<view v-if="showBenefitAmount" class="field-row">
<view class="field-block">
<view class="field-label">单价</view>
<input class="number-input" type="digit" v-model="entry.price" @input="calculateDiscount(entry)" />
</view>
<view class="field-block">
<view class="field-label">数量</view>
<input class="number-input" type="number" v-model="entry.usageCount"
@input="calculateTotalPrice(entry)" />
</view>
</view>
<view class="field-block">
<view class="field-label">总价</view>
<input class="number-input total-input" type="digit" v-model="entry.totalPrice" :disabled="entry.isFree || !entry.isDiscount" @input="calculateDiscount(entry)" />
</view>
</view>
<view v-else class="field-row">
<view class="field-block">
<view class="field-label">数量</view>
<input class="number-input" type="number" v-model="entry.usageCount" />
</view>
<view class="field-block">
<view class="field-label">有效期</view>
<view class="valid-time-wrap">
<view class="valid-time-picker" @tap="prepareValidTimePicker(entry)">
<picker
mode="multiSelector"
:range="validTimeRange"
:value="validTimeIndex"
@columnchange="onValidTimeColumnChange"
@change="changeValidTime($event, entry)"
>
<view class="picker-value" :class="entry.validTime ? 'value-text' : 'placeholder-text'">
{{ entry.validTime || "选择日期/长期" }}
</view>
<view v-if="showBenefitAmount" class="field-row">
<view class="field-block">
<view class="field-label">折扣</view>
<view class="discount-input-wrap">
<picker class="discount-picker" :class="{ 'discount-disabled': entry.isFree || !entry.isDiscount }"
mode="multiSelector" :range="getDiscountPickerRange(entry)" :value="getDiscountPickerValue(entry)"
:disabled="entry.isFree || !entry.isDiscount" @change="changeDiscount($event, entry)">
<view class="number-input picker-value value-text">{{ Number(entry.discount).toFixed(1) }}</view>
</picker>
<checkbox-group v-if="entry.isGift" class="free-check" @change="handleFreeChange($event, entry)">
<label class="free-check-label">
<checkbox value="free" :checked="entry.isFree" color="#0877f1" />
<text>赠送</text>
</label>
</checkbox-group>
</view>
<view class="unlimited-button" @click="clearValidTime(entry)">长期</view>
</view>
<view class="field-block">
<view class="field-label">总价</view>
<input class="number-input total-input" type="digit" v-model="entry.totalPrice"
:disabled="entry.isFree || !entry.isDiscount" @input="calculateDiscount(entry)" />
</view>
</view>
</view>
<view v-if="showBenefitAmount" class="field-row">
<view class="field-block field-block-full">
<view class="field-label">有效期</view>
<view class="valid-time-wrap">
<view class="valid-time-picker" @tap="prepareValidTimePicker(entry)">
<picker
mode="multiSelector"
:range="validTimeRange"
:value="validTimeIndex"
@columnchange="onValidTimeColumnChange"
@change="changeValidTime($event, entry)"
>
<view class="picker-value" :class="entry.validTime ? 'value-text' : 'placeholder-text'">
{{ entry.validTime || "选择日期/长期" }}
</view>
</picker>
<view v-else class="field-row">
<view class="field-block">
<view class="field-label">数量</view>
<input class="number-input" type="number" v-model="entry.usageCount" />
</view>
<view class="field-block">
<view class="field-label">有效期</view>
<view class="valid-time-wrap">
<view class="valid-time-picker" @tap="prepareValidTimePicker(entry)">
<picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex"
@columnchange="onValidTimeColumnChange" @change="changeValidTime($event, entry)">
<view class="picker-value" :class="entry.validTime ? 'value-text' : 'placeholder-text'">
{{ entry.validTime || "选择日期/长期" }}
</view>
</picker>
</view>
<view class="unlimited-button" @click="clearValidTime(entry)">长期</view>
</view>
<view class="unlimited-button" @click="clearValidTime(entry)">长期</view>
</view>
</view>
</view>
<view class="field-group">
<view class="field-label">治疗科室</view>
<picker
:range="getProjectDepts(entry)"
range-key="deptName"
:disabled="!entry.project?._id || !getProjectDepts(entry).length"
@change="changeDept($event, entry)"
>
<view class="project-input" :class="{ 'required-select': !entry.dept }">
<text :class="entry.dept ? 'value-text' : 'placeholder-text'">
{{ entry.dept?.deptName || (entry.project ? "该项目未配置治疗科室" : "请先选择权益项目") }}
</text>
<uni-icons type="right" size="18" color="#999" />
<view v-if="showBenefitAmount" class="field-row">
<view class="field-block field-block-full">
<view class="field-label">有效期</view>
<view class="valid-time-wrap">
<view class="valid-time-picker" @tap="prepareValidTimePicker(entry)">
<picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex"
@columnchange="onValidTimeColumnChange" @change="changeValidTime($event, entry)">
<view class="picker-value" :class="entry.validTime ? 'value-text' : 'placeholder-text'">
{{ entry.validTime || "选择日期/长期" }}
</view>
</picker>
</view>
<view class="unlimited-button" @click="clearValidTime(entry)">长期</view>
</view>
</view>
</picker>
</view>
<view class="field-group">
<view class="field-label">计划治疗医生</view>
<picker
:range="getTreatmentDoctors(entry)"
range-key="anotherName"
:disabled="!entry.dept?._id || !getTreatmentDoctors(entry).length"
@change="changeTreatmentDoctor($event, entry)"
>
<view class="project-input">
<text :class="entry.treatmentDoctorUserId ? 'value-text' : 'placeholder-text'">
{{ entry.treatmentDoctorName || (entry.dept ? "选择治疗医生" : "请先选择治疗科室") }}
</text>
<uni-icons type="right" size="18" color="#999" />
</view>
<view class="field-group">
<view class="field-label">治疗科室</view>
<picker :range="getProjectDepts(entry)" range-key="deptName"
:disabled="!entry.project?._id || !getProjectDepts(entry).length" @change="changeDept($event, entry)">
<view class="project-input" :class="{ 'required-select': !entry.dept }">
<text :class="entry.dept ? 'value-text' : 'placeholder-text'">
{{ entry.dept?.deptName || (entry.project ? "该项目未配置治疗科室" : "请先选择权益项目") }}
</text>
<uni-icons type="right" size="18" color="#999" />
</view>
</picker>
</view>
<view class="field-group">
<view class="field-label">计划治疗医生</view>
<picker :range="getTreatmentDoctors(entry)" range-key="anotherName"
:disabled="!entry.dept?._id || !getTreatmentDoctors(entry).length"
@change="changeTreatmentDoctor($event, entry)">
<view class="project-input">
<text :class="entry.treatmentDoctorUserId ? 'value-text' : 'placeholder-text'">
{{ entry.treatmentDoctorName || (entry.dept ? "选择治疗医生" : "请先选择治疗科室") }}
</text>
<uni-icons type="right" size="18" color="#999" />
</view>
</picker>
</view>
<view v-if="entry.project?._id" class="todo-switch-row">
<text>生成待办</text>
<switch :checked="entry.todoEnabled" color="#0877f1" @change="toggleTodo($event, entry)" />
</view>
<view v-if="entry.project?._id && entry.todoEnabled" class="todo-config-card">
<input v-model="entry.todoConfig.taskContent" class="todo-input" placeholder="任务内容" />
<picker :range="todoTypeOptions" range-key="label" @change="changeTodoType($event, entry)">
<view class="project-input"><text>类型:{{ getTodoTypeLabel(entry.todoConfig.eventType) }}</text><uni-icons
type="right" size="16" color="#999" /></view>
</picker>
<view class="field-row">
<view class="field-block">
<view class="field-label">回访日期</view>
<view class="valid-time-picker" @tap="prepareTodoDatePicker(entry, 'planDate')">
<picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex"
@columnchange="onValidTimeColumnChange" @change="changeTodoDate($event)">
<view class="picker-value value-text">{{ entry.todoConfig.planDate }}</view>
</picker>
</view>
</view>
<view class="field-block">
<view class="field-label">截止日期</view>
<view class="valid-time-picker" @tap="prepareTodoDatePicker(entry, 'expireDate')">
<picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex"
@columnchange="onValidTimeColumnChange" @change="changeTodoDate($event)">
<view class="picker-value value-text">{{ entry.todoConfig.expireDate }}</view>
</picker>
</view>
</view>
</view>
</picker>
</view>
<view v-if="entry.project?._id" class="todo-switch-row">
<text>生成待办</text>
<switch :checked="entry.todoEnabled" color="#0877f1" @change="toggleTodo($event, entry)" />
</view>
<view v-if="entry.project?._id && entry.todoEnabled" class="todo-config-card">
<input v-model="entry.todoConfig.taskContent" class="todo-input" placeholder="任务内容" />
<picker :range="todoTypeOptions" range-key="label" @change="changeTodoType($event, entry)"><view class="project-input"><text>类型:{{ getTodoTypeLabel(entry.todoConfig.eventType) }}</text><uni-icons type="right" size="16" color="#999" /></view></picker>
<view class="field-row"><view class="field-block"><view class="field-label">回访日期</view><view class="valid-time-picker" @tap="prepareTodoDatePicker(entry, 'planDate')"><picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex" @columnchange="onValidTimeColumnChange" @change="changeTodoDate($event)"><view class="picker-value value-text">{{ entry.todoConfig.planDate }}</view></picker></view></view><view class="field-block"><view class="field-label">截止日期</view><view class="valid-time-picker" @tap="prepareTodoDatePicker(entry, 'expireDate')"><picker mode="multiSelector" :range="validTimeRange" :value="validTimeIndex" @columnchange="onValidTimeColumnChange" @change="changeTodoDate($event)"><view class="picker-value value-text">{{ entry.todoConfig.expireDate }}</view></picker></view></view></view>
<view class="project-input" :class="{ 'required-select': !entry.todoConfig.executorUserId }" @tap="openTodoExecutorPicker(entry)"><text :class="entry.todoConfig.executorUserId ? 'value-text' : 'placeholder-text'">{{ entry.todoConfig.executorName || '请选择团队和处理人' }}</text><uni-icons type="right" size="16" color="#999" /></view>
<view class="todo-deduct-row"><text>完成关联核销权益</text><switch :checked="entry.todoConfig.deductBenefit" color="#0877f1" @change="entry.todoConfig.deductBenefit = $event.detail.value" /></view>
</view>
<view class="project-input" :class="{ 'required-select': !entry.todoConfig.executorUserId }"
@tap="openTodoExecutorPicker(entry)"><text
:class="entry.todoConfig.executorUserId ? 'value-text' : 'placeholder-text'">{{
entry.todoConfig.executorName || '请选择团队和处理人' }}</text><uni-icons type="right" size="16" color="#999" />
</view>
<view class="todo-deduct-row"><text>完成关联核销权益</text>
<switch :checked="entry.todoConfig.deductBenefit" color="#0877f1"
@change="entry.todoConfig.deductBenefit = $event.detail.value" />
</view>
</view>
<view v-if="entries.length > 1" class="remove-entry" @click="removeEntry(index)">删除此项目</view>
<view v-if="entries.length > 1" class="remove-entry" @click="removeEntry(index)">删除此项目</view>
</template>
</view>
@ -290,22 +332,21 @@
<view v-if="todoExecutorPickerVisible" class="picker-mask" @click="closeTodoExecutorPicker">
<view class="todo-executor-picker" :style="{ '--keyboard-height': `${keyboardHeight}px` }" @click.stop>
<view class="picker-header"><text class="picker-title">请选择团队和处理人</text><uni-icons type="closeempty" size="22" color="#666" @click="closeTodoExecutorPicker" /></view>
<view class="picker-header"><text class="picker-title">请选择团队和处理人</text><uni-icons type="closeempty" size="22"
color="#666" @click="closeTodoExecutorPicker" /></view>
<view class="todo-executor-search">
<uni-icons type="search" size="18" color="#999" />
<input v-model="todoExecutorKeyword" class="todo-executor-search-input" :adjust-position="false" placeholder="搜索团队或处理人" />
<input v-model="todoExecutorKeyword" class="todo-executor-search-input" :adjust-position="false"
placeholder="搜索团队或处理人" />
</view>
<scroll-view
scroll-y
class="todo-executor-list"
:refresher-enabled="true"
:refresher-triggered="todoExecutorRefreshing"
@refresherrefresh="refreshTodoExecutors"
>
<scroll-view scroll-y class="todo-executor-list" :refresher-enabled="true"
:refresher-triggered="todoExecutorRefreshing" @refresherrefresh="refreshTodoExecutors">
<view v-for="team in filteredTodoTeams" :key="team.teamId" class="todo-team-group">
<view class="todo-team-title">{{ team.name }}</view>
<view v-for="member in getTodoExecutorsForTeam(team)" :key="member.userid" class="todo-executor-item" @tap="selectTodoExecutor(member, team)">
<text>{{ member.name }}</text><text v-if="isSelectedTodoExecutor(member, team)" class="todo-selected">已选择</text>
<view v-for="member in getTodoExecutorsForTeam(team)" :key="member.userid" class="todo-executor-item"
@tap="selectTodoExecutor(member, team)">
<text>{{ member.name }}</text><text v-if="isSelectedTodoExecutor(member, team)"
class="todo-selected">已选择</text>
</view>
<view v-if="!getTodoExecutorsForTeam(team).length" class="todo-empty">暂无团队成员</view>
</view>
@ -321,7 +362,8 @@
</view>
<view class="search-box">
<uni-icons type="search" size="18" color="#999" />
<input v-model="projectKeyword" class="search-input" :adjust-position="false" placeholder="输入项目或套餐名称搜索" @input="searchProjects" />
<input v-model="projectKeyword" class="search-input" :adjust-position="false" placeholder="输入项目或套餐名称搜索"
@input="searchProjects" />
</view>
<scroll-view scroll-y class="project-list" @scrolltolower="loadMoreProjects">
<view v-if="projectList.length" class="picker-group-title">单项目</view>
@ -334,16 +376,51 @@
<view v-if="showBenefitAmount" class="project-price">¥{{ Number(project.price || 0).toFixed(2) }}</view>
</view>
<view v-if="packageList.length" class="picker-group-title">套餐</view>
<view v-for="packageItem in packageList" :key="`package_${packageItem._id}`" class="project-item" @click="selectPackage(packageItem)">
<view class="project-info"><view class="project-name">{{ packageItem.name }}</view><view class="project-depts">{{ packageItem.packageType === "free" ? `自由组合:${packageItem.projects.length}选${packageItem.projectNumber}` : "固定组合" }} · {{ (packageItem.projects || []).map((item) => item.projectName).join("、") }}</view></view>
<view v-if="showBenefitAmount" class="project-price">¥{{ Number(packageItem.packagePrice || 0).toFixed(2) }}</view>
<view v-for="packageItem in packageList" :key="`package_${packageItem._id}`" class="project-item"
@click="selectPackage(packageItem)">
<view class="project-info">
<view class="project-name">{{ packageItem.name }}</view>
<view class="project-depts">{{ packageItem.packageType === "free" ?
`自由组合:${packageItem.projects.length}选${packageItem.projectNumber}` : "固定组合" }} · {{
(packageItem.projects ||
[]).map((item) => item.projectName).join("、") }}</view>
</view>
<view v-if="showBenefitAmount" class="project-price">¥{{ Number(packageItem.packagePrice || 0).toFixed(2)
}}
</view>
</view>
<view v-if="!projectLoading && !projectList.length && !packageList.length" class="empty-text">暂无可用权益项目或套餐
</view>
<view v-if="!projectLoading && !projectList.length && !packageList.length" class="empty-text">暂无可用权益项目或套餐</view>
<view v-if="projectLoading" class="empty-text">加载中...</view>
<view v-else-if="projectList.length && !projectHasMore" class="empty-text">已加载全部权益项目</view>
</scroll-view>
</view>
</view>
<view v-if="billDeptPickerVisible" class="picker-mask" @click="closeBillDeptPicker">
<view class="project-picker bill-dept-modal" @click.stop>
<view class="picker-header">
<text class="picker-title">请选择开单科室</text>
<uni-icons type="closeempty" size="22" color="#666" @click="closeBillDeptPicker" />
</view>
<view class="search-box">
<uni-icons type="search" size="18" color="#999" />
<input v-model="billDeptKeyword" class="search-input" :adjust-position="false" placeholder="搜索科室名称" />
</view>
<scroll-view scroll-y class="bill-dept-list">
<view v-for="item in filteredBillDeptOptions" :key="item._id" class="bill-dept-option"
:style="{ paddingLeft: `${20 + item.level * 36}rpx` }" @tap="selectBillDept(item)">
<uni-icons v-if="item.children?.length" :type="item.expanded ? 'bottom' : 'right'" size="16" color="#999"
@tap.stop="toggleBillDept(item)" />
<view v-else class="bill-dept-option-spacer"></view>
<text :class="item._id === billDeptId ? 'bill-dept-option-name selected' : 'bill-dept-option-name'">{{
item.deptName }}</text>
<text v-if="item.children?.length" class="bill-dept-level-tag">多级</text>
<uni-icons v-if="item._id === billDeptId" type="checkmarkempty" size="18" color="#0877f1" />
</view>
<view v-if="!filteredBillDeptOptions.length" class="empty-text">暂无匹配科室</view>
</scroll-view>
</view>
</view>
</view>
<template #footer>
@ -356,11 +433,8 @@
</view>
<text class="detail-trigger" @tap="toggleBenefitDetail">明细</text>
</view>
<view
v-if="showBenefitAmount"
class="benefit-detail-panel"
:class="{ 'benefit-detail-panel-visible': benefitDetailVisible }"
>
<view v-if="showBenefitAmount" class="benefit-detail-panel"
:class="{ 'benefit-detail-panel-visible': benefitDetailVisible }">
<view class="detail-header">
<text class="detail-project-name">权益项目</text>
<text class="detail-quantity">数量</text>
@ -393,7 +467,6 @@ import { onLoad } from "@dcloudio/uni-app";
import api from "@/utils/api.js";
import useAccountStore from "@/store/account.js";
import fullPage from "@/components/full-page.vue";
import UniDataPicker from "@/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue";
import { TODO_EVENT_TYPE_LABELS } from "@/utils/todo-const.js";
const PENDING_FOLLOWUP_SEND_STORAGE_KEY = "ykt_followup_pending_send";
@ -413,6 +486,9 @@ const billDeptId = ref("");
const billDeptName = ref("");
const billDoctorUserId = ref("");
const billDoctorName = ref("");
const billDeptKeyword = ref("");
const billDeptPickerVisible = ref(false);
const billDeptExpanded = ref(new Set());
const entries = ref([]);
const saving = ref(false);
const projectPickerVisible = ref(false);
@ -777,16 +853,6 @@ function normalizeProject(project = {}) {
};
}
function changeBillDept(event) {
const selected = event?.detail?.value;
const dept = Array.isArray(selected) ? selected[selected.length - 1] : null;
billDeptId.value = dept?.value ? String(dept.value) : "";
billDeptName.value = dept?.text || dept?.deptName || "";
billDeptValue.value = billDeptId.value;
billDoctorUserId.value = "";
billDoctorName.value = "";
}
function getBillDoctors() {
if (!billDeptId.value) return [];
const deptId = String(billDeptId.value);
@ -804,6 +870,57 @@ function changeBillDoctor(event) {
billDoctorName.value = doctor?.anotherName || doctor?.name || "";
}
const filteredBillDeptOptions = computed(() => {
const keyword = billDeptKeyword.value.trim().toLowerCase();
const result = [];
const filterTree = (nodes) => (nodes || []).reduce((matchedNodes, node) => {
const name = String(node.deptName || node.name || "").toLowerCase();
const children = filterTree(node.children);
if (!keyword || name.includes(keyword) || children.length) {
matchedNodes.push({ ...node, ...(children.length ? { children } : {}) });
}
return matchedNodes;
}, []);
const walk = (nodes, level = 0) => (nodes || []).forEach((node) => {
const children = node.children || [];
const matched = String(node.deptName || node.name || "").toLowerCase().includes(keyword);
if (!keyword || matched || children.length) {
const expanded = Boolean(keyword) || billDeptExpanded.value.has(String(node._id));
result.push({ ...node, level, expanded });
if (expanded) walk(children, level + 1);
}
});
walk(keyword ? filterTree(billDeptOptions.value) : billDeptOptions.value);
return result;
});
function openBillDeptPicker() {
billDeptPickerVisible.value = true;
billDeptKeyword.value = "";
billDeptExpanded.value = new Set(billDeptOptions.value.filter((item) => item.children?.length).map((item) => String(item._id)));
}
function closeBillDeptPicker() {
billDeptPickerVisible.value = false;
}
function toggleBillDept(item) {
const next = new Set(billDeptExpanded.value);
const id = String(item._id);
if (next.has(id)) next.delete(id);
else next.add(id);
billDeptExpanded.value = next;
}
function selectBillDept(dept) {
billDeptId.value = String(dept._id || "");
billDeptName.value = dept.deptName || dept.name || "";
billDeptValue.value = billDeptId.value;
billDoctorUserId.value = "";
billDoctorName.value = "";
closeBillDeptPicker();
}
function buildDeptTree(deptList) {
const deptMap = new Map();
const rootDepts = [];
@ -1905,6 +2022,29 @@ async function submitEntries() {
background: #fff;
}
.dept-search-input {
display: flex;
align-items: center;
height: 68rpx;
margin-bottom: 12rpx;
padding: 0 18rpx;
box-sizing: border-box;
border: 1rpx solid #d1d5db;
border-radius: 12rpx;
background: #fff;
}
.dept-search-field {
flex: 1;
min-width: 0;
height: 64rpx;
margin-left: 10rpx;
border: 0;
background: transparent;
color: #111827;
font-size: 28rpx;
}
.bill-dept-picker :deep(.uni-data-tree-input) {
min-height: 76rpx;
}
@ -2124,7 +2264,7 @@ async function submitEntries() {
border-top: 1rpx solid #eef2f7;
box-shadow: 0 -4rpx 16rpx rgba(15, 23, 42, 0.04);
box-sizing: border-box;
z-index: 10;
z-index: 1;
}
.footer-summary {
@ -2291,10 +2431,11 @@ async function submitEntries() {
.picker-mask {
position: fixed;
inset: 0;
z-index: 999;
z-index: 10000;
background: rgba(0, 0, 0, 0.4);
display: flex;
align-items: flex-end;
box-sizing: border-box;
}
.project-picker {
@ -2315,6 +2456,48 @@ async function submitEntries() {
box-sizing: border-box;
}
.bill-dept-modal {
height: 70vh;
}
.bill-dept-list {
flex: 1;
min-height: 0;
margin-top: 20rpx;
}
.bill-dept-option {
display: flex;
align-items: center;
min-height: 76rpx;
border-bottom: 1rpx solid #f0f0f0;
color: #374151;
}
.bill-dept-option-spacer {
width: 16rpx;
flex-shrink: 0;
}
.bill-dept-option-name {
margin-left: 10rpx;
font-size: 28rpx;
}
.bill-dept-option-name.selected {
color: #0877f1;
font-weight: 600;
}
.bill-dept-level-tag {
margin-left: 12rpx;
padding: 2rpx 8rpx;
border-radius: 6rpx;
background: #eef6ff;
color: #0877f1;
font-size: 20rpx;
}
.picker-header {
display: flex;
justify-content: space-between;