This commit is contained in:
zlwu committed 2026-05-26 19:04:27 +08:00
1 parent 4a00f21bfb
commit edf88dc016
1503 files changed
+329728 -41987

No files matched your search

@@ -59,7 +59,11 @@
/>
</el-select>
</el-form-item>
<el-form-item label="服务订单" prop="channelServiceOrderId" class="form-col-span-2-cols">
<el-form-item
label="服务订单"
prop="channelServiceOrderId"
class="form-col-span-2-cols"
>
<div class="field-with-action">
<el-select
v-model="formData.channelServiceOrderId"
@@ -227,7 +231,9 @@
</template>
</div>
<div class="attachment-footer">
<span class="attachment-name" :title="file.fileName">{{ file.fileName }}</span>
<span class="attachment-name" :title="file.fileName">{{
file.fileName
}}</span>
<div class="attachment-actions">
<el-icon class="action-btn" @click.stop="downloadFile(file)">
<Download />
@@ -305,10 +311,7 @@
</el-form-item>
<el-form-item>
<template #label>疗程间隔<span class="form-label-tip">(月)</span></template>
<el-input
v-model="infusionPlan.courseInterval"
placeholder="请输入疗程间隔"
/>
<el-input v-model="infusionPlan.courseInterval" placeholder="请输入疗程间隔" />
</el-form-item>
<el-form-item label="输注开始日期">
<el-date-picker
@@ -353,7 +356,24 @@
<div class="schedule-header" @click="toggleScheduleExpand(sIdx)">
<span class="schedule-chevron">▶</span>
<span class="schedule-title">输注计划 #{{ sIdx + 1 }}</span>
<el-tag
v-if="schedule.infusionStatus"
:type="infusionStatusTagType(normalizeInfusionStatusCode(schedule.infusionStatus))"
size="small"
class="schedule-status-tag"
>
{{ infusionStatusLabel(normalizeInfusionStatusCode(schedule.infusionStatus)) }}
</el-tag>
<el-tag
v-if="schedule.scheduleStatus && isEdit"
:type="scheduleStatusTagType(schedule.scheduleStatus)"
size="small"
class="schedule-status-tag"
>
{{ scheduleStatusLabel(schedule.scheduleStatus) }}
</el-tag>
<el-button
v-if="canDeleteSchedule(schedule)"
link
type="danger"
size="small"
@@ -373,6 +393,7 @@
value-format="YYYY-MM-DD"
placeholder="自动计算,可修改"
clearable
:disabled="!isScheduleEditable(schedule)"
style="width: 100%"
/>
</el-form-item>
@@ -380,6 +401,7 @@
<el-input
v-model="schedule.infusionLocation"
placeholder="请输入输注地点"
:disabled="!isScheduleEditable(schedule)"
/>
</el-form-item>
<el-form-item label="输注方式">
@@ -388,6 +410,7 @@
clearable
filterable
placeholder="请选择输注方式"
:disabled="!isScheduleEditable(schedule)"
style="width: 100%"
>
<el-option
@@ -407,7 +430,7 @@
<el-button
type="primary"
size="small"
:disabled="!canAddCellProduct"
:disabled="!canAddCellProduct || !isScheduleEditable(schedule)"
@click="addCellProduct(sIdx)"
>
<el-icon><Plus /></el-icon>
@@ -428,10 +451,8 @@
clearable
placeholder="选择项目"
class="cell-product-table__control"
:disabled="!canAddCellProduct"
@change="
onCellProductSelect(sIdx, pIdx, $event)
"
:disabled="!canAddCellProduct || !isScheduleEditable(schedule)"
@change="onCellProductSelect(sIdx, pIdx, $event)"
>
<el-option
v-for="p in getCellProductPriceOptions(sIdx, pIdx)"
@@ -442,16 +463,8 @@
</el-select>
</template>
</el-table-column>
<el-table-column
prop="cellTypeName"
label="细胞类型"
min-width="100"
/>
<el-table-column
prop="specText"
label="规格"
min-width="100"
/>
<el-table-column prop="cellTypeName" label="细胞类型" min-width="100" />
<el-table-column prop="specText" label="规格" min-width="100" />
<el-table-column label="协议单价(元)" min-width="130" align="right">
<template #default="{ row }">
<span class="cell-product-price-text">
@@ -465,6 +478,7 @@
v-model="row.actualPrice"
:min="0"
:precision="2"
:disabled="!isScheduleEditable(schedule)"
controls-position="right"
class="cell-product-table__control"
/>
@@ -475,6 +489,7 @@
<el-input-number
v-model="row.quantity"
:min="1"
:disabled="!isScheduleEditable(schedule)"
controls-position="right"
class="cell-product-table__control"
/>
@@ -485,18 +500,27 @@
<el-input
v-model="row.remark"
placeholder="备注"
:disabled="!isScheduleEditable(schedule)"
class="cell-product-table__control"
/>
</template>
</el-table-column>
<el-table-column label="状态" min-width="80" align="center">
<template #default="{ row }">
<el-tag size="small" type="info">{{ row.status }}</el-tag>
<el-tag
:type="infusionStatusTagType(normalizeInfusionStatusCode(row.status))"
size="small"
>
{{
infusionStatusLabel(normalizeInfusionStatusCode(row.status))
}}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="60" align="center">
<template #default="{ $index: pIdx }">
<el-button
v-if="isScheduleEditable(schedule)"
link
type="danger"
size="small"
@@ -622,15 +646,45 @@
class="list-table inner-table"
max-height="360"
>
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
<el-table-column prop="projectName" label="项目名称" min-width="150" show-overflow-tooltip />
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
/>
<el-table-column
prop="projectName"
label="项目名称"
min-width="150"
show-overflow-tooltip
/>
<el-table-column prop="cellType" label="细胞类型" min-width="120" show-overflow-tooltip />
<el-table-column prop="specText" label="规格文本" min-width="150" show-overflow-tooltip />
<el-table-column label="协议单价(元)" min-width="130" align="right" header-align="center">
<template #default="{ row }">¥{{ formatOrderBalance(row.agreementUnitPrice) }}</template>
<el-table-column
label="协议单价(元)"
min-width="130"
align="right"
header-align="center"
>
<template #default="{ row }"
>¥{{ formatOrderBalance(row.agreementUnitPrice) }}</template
>
</el-table-column>
<el-table-column prop="currentQty" label="当前持有" min-width="100" align="center" header-align="center" />
<el-table-column prop="totalBuyQty" label="累计购买" min-width="100" align="center" header-align="center" />
<el-table-column
prop="currentQty"
label="当前持有"
min-width="100"
align="center"
header-align="center"
/>
<el-table-column
prop="totalBuyQty"
label="累计购买"
min-width="100"
align="center"
header-align="center"
/>
<el-table-column
prop="totalUnsubscribeQty"
label="累计退订"
@@ -668,12 +722,38 @@
class="list-table inner-table"
max-height="360"
>
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
<el-table-column prop="projectName" label="项目名称" min-width="140" show-overflow-tooltip />
<el-table-column prop="projectCode" label="项目编码" min-width="110" show-overflow-tooltip />
<el-table-column prop="cellTypeName" label="细胞类型" min-width="110" show-overflow-tooltip />
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
/>
<el-table-column
prop="projectName"
label="项目名称"
min-width="140"
show-overflow-tooltip
/>
<el-table-column
prop="projectCode"
label="项目编码"
min-width="110"
show-overflow-tooltip
/>
<el-table-column
prop="cellTypeName"
label="细胞类型"
min-width="110"
show-overflow-tooltip
/>
<el-table-column prop="specText" label="规格" min-width="120" show-overflow-tooltip />
<el-table-column label="协议单价(元)" min-width="130" align="right" header-align="center">
<el-table-column
label="协议单价(元)"
min-width="130"
align="right"
header-align="center"
>
<template #default="{ row }">¥{{ formatOrderBalance(row.unitPrice) }}</template>
</el-table-column>
</el-table>
@@ -685,7 +765,17 @@
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import { Plus, Delete, Close, Check, Download, Document, VideoPlay, Loading, View } from '@element-plus/icons-vue'
import {
Plus,
Delete,
Close,
Check,
Download,
Document,
VideoPlay,
Loading,
View
} from '@element-plus/icons-vue'
import {
savePilotApplication,
fetchPilotApplicationDetail,
@@ -702,7 +792,9 @@
import { fetchUploadAttachment } from '@/api/business'
import { fetchAllPagedRecords } from '@/utils/fetch-paged-list'
import DialogFormBody from '@/components/business/dialog-form-body/index.vue'
import SystemViewDialog, { type ViewSection } from '@/components/business/system-view-dialog/index.vue'
import SystemViewDialog, {
type ViewSection
} from '@/components/business/system-view-dialog/index.vue'
import ServiceOrderTxnTable from '@/views/pilot-service/channel-service-order/ServiceOrderTxnTable.vue'
import {
channelServiceOrderTypeLabel,
@@ -714,7 +806,11 @@
agreementStatusLabel,
agreementStatusTagType,
settlementTypeLabel,
settlementTypeTagType
settlementTypeTagType,
infusionStatusLabel,
infusionStatusTagType,
scheduleStatusLabel,
scheduleStatusTagType
} from '@/utils/status-tag-registry'
import { formatMoneyDisplay } from '@/utils/money-format'
@@ -1250,7 +1346,11 @@
const remoteSearchCustomers = async (keyword: string) => {
customerLoading.value = true
try {
const res: any = await fetchCustomerList({ page: 1, pageSize: 50, keyword: keyword || undefined })
const res: any = await fetchCustomerList({
page: 1,
pageSize: 50,
keyword: keyword || undefined
})
const data = res?.data ?? res
customerOptions.value = data?.list ?? data?.records ?? []
} catch {
@@ -1381,7 +1481,7 @@
fileSize: file.size,
fileSizeDisplay: `${(file.size / 1024).toFixed(1)} KB`,
fileType: file.type || '',
_uploading: true,
_uploading: true
})
const idx = diagnosisFileList.value.length - 1
@@ -1397,7 +1497,7 @@
fileSize: res.fileSize || file.size,
fileSizeDisplay: res.fileSizeDisplay || `${(file.size / 1024).toFixed(1)} KB`,
fileType: res.fileType || file.type || '',
_uploading: false,
_uploading: false
}
} catch (e: any) {
ElMessage.error(e?.message || '上传失败')
@@ -1423,12 +1523,40 @@
// ── 输注方案(inline,一个申请单一个方案) ──────────────────────────────
const infusionPlan = reactive({
id: '' as string,
totalCourseCount: 0,
courseInterval: '',
infusionStartDate: '',
channelAgreementId: ''
})
const INFUSION_STATUS_CN_TO_CODE: Record<string, string> = {
待输注: 'dtz',
已用户确认: 'yrqr',
已取消: 'yqx',
已完成: 'ywc'
}
function normalizeInfusionStatusCode(raw: unknown): string {
const s = String(raw ?? '').trim()
if (!s) return ''
return INFUSION_STATUS_CN_TO_CODE[s] || s
}
/** 待输注 / 已用户确认:可编辑时间与细胞产品 */
function isScheduleEditable(schedule: Record<string, any>): boolean {
if (!isEdit.value) return true
const st = normalizeInfusionStatusCode(schedule.infusionStatus)
return st === 'dtz' || st === 'yrqr'
}
/** 仅待输注可删疗程 */
function canDeleteSchedule(schedule: Record<string, any>): boolean {
if (!isEdit.value) return true
if (!schedule.id) return true
return normalizeInfusionStatusCode(schedule.infusionStatus) === 'dtz'
}
// 输注计划列表
let scheduleSeq = 0
const infusionSchedules = ref<any[]>([])
@@ -1574,6 +1702,11 @@
}
const removeSchedule = (idx: number) => {
const s = infusionSchedules.value[idx]
if (s && !canDeleteSchedule(s)) {
ElMessage.warning('当前疗程不可删除(仅待输注且未开始生产的计划可删)')
return
}
infusionSchedules.value.splice(idx, 1)
recalcAllScheduleTimes()
syncTotalCourseCountWithSchedules()
@@ -1583,6 +1716,10 @@
const addCellProduct = (scheduleIdx: number) => {
const s = infusionSchedules.value[scheduleIdx]
if (!s) return
if (!isScheduleEditable(s)) {
ElMessage.warning('当前疗程状态不允许修改细胞产品')
return
}
const usedKeys = new Set(
(s.cellProducts || []).map((cp: any) => getCellProductProjectKey(cp)).filter(Boolean)
)
@@ -1605,13 +1742,17 @@
actualPrice: 0,
quantity: 1,
remark: '',
status: '待输注'
status: 'dtz'
})
}
const removeCellProduct = (scheduleIdx: number, productIdx: number) => {
const s = infusionSchedules.value[scheduleIdx]
if (!s) return
if (!isScheduleEditable(s)) {
ElMessage.warning('当前疗程状态不允许修改细胞产品')
return
}
s.cellProducts.splice(productIdx, 1)
}
@@ -1701,6 +1842,7 @@
patientForm.clinicalDiagnosis = ''
patientForm.needCheckup = 0
diagnosisFileList.value = []
infusionPlan.id = ''
infusionPlan.totalCourseCount = 0
infusionPlan.courseInterval = ''
infusionPlan.infusionStartDate = ''
@@ -1775,7 +1917,7 @@
fileSize: 0,
fileSizeDisplay: '',
fileType: '',
_uploading: false,
_uploading: false
}
})
}
@@ -1788,6 +1930,7 @@
try {
if (detail.infusionPlan) {
const ip = detail.infusionPlan
infusionPlan.id = ip.id || ''
infusionPlan.totalCourseCount = ip.totalCourseCount || 0
infusionPlan.courseInterval = ip.courseInterval || ''
infusionPlan.infusionStartDate = ip.infusionStartDate || ''
@@ -1795,12 +1938,16 @@
}
if (detail.infusionSchedules && Array.isArray(detail.infusionSchedules)) {
infusionSchedules.value = detail.infusionSchedules.map((s: any, i: number) => ({
id: s.id || '',
_localId: ++scheduleSeq,
infusionTime: s.infusionTime || '',
infusionLocation: s.infusionLocation || '',
infusionMethod: s.infusionMethod || '',
infusionStatus: normalizeInfusionStatusCode(s.infusionStatus) || 'dtz',
scheduleStatus: s.scheduleStatus || 'dsc',
_expanded: i === 0,
cellProducts: (s.cellProducts || []).map((cp: any) => ({
id: cp.id || '',
_localId: ++scheduleSeq,
priceId: cp.priceId || '',
projectCode: cp.projectCode || '',
@@ -1811,7 +1958,7 @@
actualPrice: cp.actualPrice ?? 0,
quantity: cp.quantity || 1,
remark: cp.remark || '',
status: cp.status || '待输注'
status: normalizeInfusionStatusCode(cp.status) || 'dtz'
}))
}))
}
@@ -1856,21 +2003,28 @@
emergencyContact: patientForm.emergencyContact || undefined,
emergencyPhone: patientForm.emergencyPhone || undefined,
clinicalDiagnosis: patientForm.clinicalDiagnosis || undefined,
diagnosisAttachments: diagnosisFileList.value.map((f) => f.fileUrl).filter(Boolean).join(',') || undefined,
diagnosisAttachments:
diagnosisFileList.value
.map((f) => f.fileUrl)
.filter(Boolean)
.join(',') || undefined,
needCheckup: patientForm.needCheckup
},
infusionPlan: {
id: infusionPlan.id || undefined,
totalCourseCount: infusionPlan.totalCourseCount,
courseInterval: infusionPlan.courseInterval || undefined,
infusionStartDate: infusionPlan.infusionStartDate || undefined,
channelAgreementId: infusionPlan.channelAgreementId || undefined
},
infusionSchedules: infusionSchedules.value.map((s, idx) => ({
id: s.id || undefined,
sortNo: idx + 1,
infusionTime: s.infusionTime || undefined,
infusionLocation: s.infusionLocation || undefined,
infusionMethod: s.infusionMethod || undefined,
cellProducts: (s.cellProducts || []).map((cp: any) => ({
id: cp.id || undefined,
priceId: cp.priceId || undefined,
projectCode: cp.projectCode || undefined,
projectName: cp.projectName || undefined,
@@ -1879,8 +2033,7 @@
agreementPrice: cp.agreementPrice ?? undefined,
actualPrice: cp.actualPrice ?? undefined,
quantity: cp.quantity || 1,
remark: cp.remark || undefined,
status: cp.status || '待输注'
remark: cp.remark || undefined
}))
}))
}
@@ -2370,6 +2523,10 @@
color: #1e293b;
}
.schedule-status-tag {
margin-left: 8px;
}
.schedule-delete-btn {
margin-left: auto;
}