Files
zkxw/web-admin/src/views/cell-storage/presign-order/PresignOrderFormDialog.vue
T

1015 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 预签约订单 新增/编辑 -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<el-dialog
:model-value="modelValue"
:title="orderId ? '编辑预签约订单' : '新增预签约订单'"
width="1200px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="protocol-dialog-mask"
destroy-on-close
:close-on-click-modal="false"
@update:model-value="emit('update:modelValue', $event)"
@closed="resetForm"
>
<DialogFormBody v-loading="loading">
<el-form ref="formRef" :model="form" label-position="top" scroll-to-error>
<div class="form-grid-3">
<el-form-item label="业务日期">
<el-date-picker
v-model="form.orderDate"
type="date"
value-format="YYYYMMDD"
format="YYYY-MM-DD"
clearable
style="width: 100%"
/>
</el-form-item>
<el-form-item label="协议编号">
<el-input v-model="form.protocolNo" placeholder="可选,转正式时可预占" clearable />
</el-form-item>
<el-form-item label="母亲姓名" required>
<el-input v-model="form.motherName" placeholder="请输入母亲姓名" clearable />
</el-form-item>
<el-form-item label="预产期">
<el-date-picker
v-model="form.expectedDeliveryDate"
type="date"
value-format="YYYYMMDD"
format="YYYY-MM-DD"
clearable
style="width: 100%"
/>
</el-form-item>
<el-form-item label="采集地区">
<el-select
v-model="form.regionId"
filterable
clearable
placeholder="请选择"
style="width: 100%"
@change="onRegionChange"
>
<el-option
v-for="opt in regionOptions"
:key="opt.id"
:label="opt.regionName"
:value="opt.id"
/>
</el-select>
</el-form-item>
<el-form-item label="医院">
<el-select
v-model="form.hospitalId"
filterable
clearable
placeholder="请选择"
style="width: 100%"
:disabled="!form.regionId"
>
<el-option
v-for="opt in hospitalOptions"
:key="opt.id"
:label="opt.hospitalName"
:value="opt.id"
/>
</el-select>
</el-form-item>
<el-form-item label="采集专员">
<el-select
v-model="form.collectorUserId"
class="w-full"
filterable
remote
clearable
reserve-keyword
:remote-method="remoteSearchCollectorUsers"
:loading="collectorLoading"
:disabled="isConverted"
placeholder="输入姓名检索用户"
style="width: 100%"
@change="onCollectorUserChange"
>
<el-option
v-for="u in collectorUserOptions"
:key="u.id"
:label="u.nickName + (u.phoneNo ? ' · ' + u.phoneNo : '')"
:value="u.id"
/>
</el-select>
</el-form-item>
<el-form-item label="采集经理">
<el-select
v-model="form.regionManagerUserId"
class="w-full"
filterable
remote
clearable
reserve-keyword
:remote-method="remoteSearchManagerUsers"
:loading="managerLoading"
:disabled="isConverted"
placeholder="默认取地区负责人,可修改"
style="width: 100%"
@change="onManagerUserChange"
>
<el-option
v-for="u in managerUserOptions"
:key="u.id"
:label="u.nickName + (u.phoneNo ? ' · ' + u.phoneNo : '')"
:value="u.id"
/>
</el-select>
</el-form-item>
<el-form-item label="采集时间">
<el-date-picker
v-model="form.sampleCollectTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
clearable
style="width: 100%"
/>
</el-form-item>
<el-form-item label="缴费方式">
<el-select
v-model="form.payMode"
clearable
placeholder="整单初步方式"
style="width: 100%"
>
<el-option label="趸交" value="dj" />
<el-option label="分期" value="fq" />
</el-select>
</el-form-item>
<el-form-item label="整单合同金额(元)">
<AmountInput v-model="form.contractAmount" :min="0" style="width: 100%" />
</el-form-item>
<el-form-item label="订单状态">
<el-select v-model="form.orderStatus" style="width: 100%" :disabled="isConverted">
<el-option label="草稿" value="draft" />
<el-option label="进行中" value="active" />
<el-option label="已转正式" value="converted" />
<el-option label="已作废" value="void" />
</el-select>
</el-form-item>
<el-form-item label="样本类型" required>
<el-select
v-model="form.sampleTypeIds"
multiple
filterable
clearable
placeholder="可多选样本类型"
style="width: 100%"
>
<el-option
v-for="opt in sampleTypeOptions"
:key="opt.id"
:label="opt.typeName || opt.sampleTypeName"
:value="opt.id"
/>
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="form.remark"
maxlength="500"
show-word-limit
placeholder="选填"
clearable
/>
</el-form-item>
</div>
<div class="section-subtitle">意向金转账</div>
<div class="transfer-toolbar">
<el-button type="primary" size="small" :disabled="isConverted" @click="addTransfer">
<el-icon><Plus /></el-icon>
添加转账笔
</el-button>
<span v-if="confirmedDeposit > 0" class="deposit-hint">
已确认意向金合计 ¥{{ formatMoneyDisplay(confirmedDeposit) }}
</span>
</div>
<el-table
:data="form.transfers"
class="list-table transfer-table"
stripe
empty-text="暂无意向金转账笔,请点击上方按钮添加"
>
<el-table-column type="index" label="#" min-width="50" align="center" />
<el-table-column label="转账方式" min-width="140" align="center">
<template #default="{ row }">
<el-select
v-model="row.transferType"
:disabled="isTransferLocked(row) || isConverted"
placeholder="请选择"
style="width: 100%"
>
<el-option
v-for="opt in transferTypeOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="转账金额(元)" min-width="140" align="center">
<template #default="{ row }">
<AmountInput
v-model="row.transferAmount"
:min="0"
:disabled="isTransferLocked(row) || isConverted"
style="width: 100%"
/>
</template>
</el-table-column>
<el-table-column label="转账时间" min-width="190" align="center">
<template #default="{ row }">
<el-date-picker
v-model="row.transferTime"
type="datetime"
format="YYYY-MM-DD HH:mm"
value-format="YYYY-MM-DD HH:mm:ss"
:disabled="isTransferLocked(row) || isConverted"
clearable
placeholder="选择时间"
style="width: 100%"
/>
</template>
</el-table-column>
<el-table-column label="转账凭证" min-width="280" align="left">
<template #default="{ row, $index }">
<div class="attachment-upload">
<div class="attachment-list">
<el-upload
v-if="!isTransferLocked(row) && !isConverted"
:auto-upload="false"
:show-file-list="false"
:multiple="true"
accept="image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar"
@change="(f: any) => handleTransferAttachmentChange($index, f)"
>
<div class="upload-card">
<el-icon :size="22" color="#047857"><Plus /></el-icon>
<span class="upload-card-text">上传凭证</span>
</div>
</el-upload>
<div
v-for="(file, fIdx) in row.attachments || []"
:key="file.id || fIdx"
class="attachment-item"
:class="{ 'is-uploading': file._uploading }"
>
<div class="attachment-preview">
<template v-if="file.fileUrl && isImageUrl(file.fileUrl)">
<img :src="fullFileUrl(file.fileUrl)" :alt="file.fileName" />
</template>
<template v-else>
<div class="attachment-preview__icon">
<el-icon :size="22"><Document /></el-icon>
</div>
</template>
</div>
<div class="attachment-footer">
<span class="attachment-name" :title="file.fileName">{{
file.fileName || '附件'
}}</span>
<el-icon
v-if="!isTransferLocked(row) && !isConverted"
class="action-btn"
title="删除"
@click="removeTransferAttachment($index, fIdx)"
>
<Delete />
</el-icon>
</div>
</div>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="备注" min-width="160" align="center">
<template #default="{ row }">
<el-input
v-if="!isTransferLocked(row) && !isConverted"
v-model="row.remark"
maxlength="512"
clearable
placeholder="选填"
/>
<span v-else>{{ row.remark || '—' }}</span>
</template>
</el-table-column>
<el-table-column
label="财务备注"
min-width="140"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.financeRemark || '—' }}</template>
</el-table-column>
<el-table-column label="状态" min-width="110" align="center">
<template #default="{ row }">
<el-tag :type="payStatusTagType(String(row.payStatus || 'dfj'))" size="small">
{{ payStatusLabel(String(row.payStatus || 'dfj')) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="220" align="center" fixed="right">
<template #default="{ row, $index }">
<el-button
v-if="row.id && row.payStatus === 'dfj' && !isConverted"
link
type="primary"
size="small"
@click="submitTransfer(row)"
>
<el-icon><Check /></el-icon>
提交财务确认
</el-button>
<el-button
v-if="row.id && row.payStatus === 'jfqrz' && !isConverted"
link
type="warning"
size="small"
@click="withdrawTransfer(row)"
>
<el-icon><RefreshLeft /></el-icon>
撤回
</el-button>
<el-button
v-if="!isTransferLocked(row) && !isConverted"
link
type="danger"
size="small"
@click="form.transfers.splice($index, 1)"
>
<el-icon><Delete /></el-icon>
删除
</el-button>
<span
v-if="
isConverted ||
(isTransferLocked(row) &&
!(row.id && (row.payStatus === 'dfj' || row.payStatus === 'jfqrz')))
"
class="muted-dash"
>—</span
>
</template>
</el-table-column>
</el-table>
</el-form>
</DialogFormBody>
<template #footer>
<el-button class="btn-ghost" @click="emit('update:modelValue', false)">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button
class="btn-main"
type="primary"
:loading="saving"
:disabled="isConverted"
@click="handleSave"
>
<el-icon><Check /></el-icon>
确定
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'
import { Plus, Close, Check, Document, Delete, RefreshLeft } from '@element-plus/icons-vue'
import AmountInput from '@/components/common/amount-input/index.vue'
import { formatMoneyDisplay } from '@/utils/money-format'
import {
payStatusLabel,
payStatusTagType,
loadTransferTypeDict,
getTransferTypeOptions
} from '@/utils/protocol-payment-labels'
import DialogFormBody from '@/components/business/dialog-form-body/index.vue'
import { useUserStore } from '@/store/modules/user'
import { fetchUploadAttachment } from '@/api/business'
import {
fetchCellSampleTypeList,
fetchCollectionRegionList,
fetchHospitalList,
fetchRegionStaffList,
fetchUserList
} from '@/api/admin'
import {
fetchPresignOrderDetail,
fetchCreatePresignOrder,
fetchUpdatePresignOrder,
fetchCreatePresignTransfer,
fetchUpdatePresignTransfer,
fetchSubmitPresignTransferFinance,
fetchWithdrawPresignTransfer
} from '@/api/presign-order'
const userStore = useUserStore()
const props = defineProps<{
modelValue: boolean
orderId?: string
}>()
const emit = defineEmits<{
'update:modelValue': [boolean]
saved: []
}>()
const formRef = ref()
const loading = ref(false)
const saving = ref(false)
const transferTypeOptions = ref<Api.Dict.DictDataOption[]>([])
const sampleTypeOptions = ref<Array<Record<string, any>>>([])
const regionOptions = ref<Array<Record<string, any>>>([])
const hospitalOptions = ref<Array<Record<string, any>>>([])
const collectorUserOptions = ref<Api.User.UserItem[]>([])
const managerUserOptions = ref<Api.User.UserItem[]>([])
const collectorLoading = ref(false)
const managerLoading = ref(false)
function todayYmd() {
const d = new Date()
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}${m}${day}`
}
function userStubFromStore(): Api.User.UserItem | null {
const currentUser = userStore.getUserInfo
const uid = String(currentUser?.id || currentUser?.userId || '')
if (!uid) return null
return {
id: uid,
nickName: String(
currentUser?.realName || currentUser?.userName || currentUser?.username || ''
),
phoneNo: String(currentUser?.phone || currentUser?.phoneNo || ''),
deptId: '',
userNo: '',
email: '',
sex: '',
avatar: '',
status: '1',
tenantNo: '',
orderNum: 0,
remark: '',
createTime: '',
updateTime: ''
} as Api.User.UserItem
}
function makeUserStub(id: string, name: string): Api.User.UserItem {
return {
id,
nickName: name || id,
phoneNo: '',
deptId: '',
userNo: '',
email: '',
sex: '',
avatar: '',
status: '1',
tenantNo: '',
orderNum: 0,
remark: '',
createTime: '',
updateTime: ''
} as Api.User.UserItem
}
const form = reactive({
orderDate: todayYmd(),
protocolNo: '',
motherName: '',
expectedDeliveryDate: '',
regionId: '',
hospitalId: '',
collectorUserId: '',
regionManagerUserId: '',
sampleCollectTime: '',
payMode: 'dj',
contractAmount: null as number | null,
orderStatus: 'active',
sampleTypeIds: [] as string[],
remark: '',
transfers: [] as Array<Record<string, any>>,
confirmedDepositAmount: 0
})
const isConverted = computed(() => form.orderStatus === 'converted')
const confirmedDeposit = computed(() => Number(form.confirmedDepositAmount || 0))
function isTransferLocked(tr: Record<string, any>) {
return tr.payStatus === 'jfqrz' || tr.payStatus === 'yjf'
}
type TransferAttachment = {
id?: string
fileName?: string
fileUrl?: string
_uploading?: boolean
}
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
function fullFileUrl(url: string) {
if (!url) return ''
if (/^https?:\/\//i.test(url)) return url
return minioBaseUrl ? `${minioBaseUrl}/${url.replace(/^\//, '')}` : url
}
function isImageUrl(url: string) {
return /\.(png|jpe?g|gif|webp|bmp|svg)(\?.*)?$/i.test(url)
}
function attachmentsToUrls(files: TransferAttachment[] | undefined) {
return (files || [])
.filter((f) => f.fileUrl && !f._uploading)
.map((f) => String(f.fileUrl))
.join(',')
}
function normalizeTransfer(row: Record<string, any>) {
const files = Array.isArray(row.attachments) ? row.attachments : []
return {
...row,
attachments: files.map((f: Record<string, any>, i: number) => ({
id: f.id || `att_${i}`,
fileName: f.fileName || f.name || '附件',
fileUrl: f.fileUrl || f.url || ''
})) as TransferAttachment[]
}
}
async function handleTransferAttachmentChange(index: number, uploadFile: { raw?: File }) {
const file = uploadFile.raw
if (!file) return
if (file.size > 20 * 1024 * 1024) {
ElMessage.warning('单文件不超过 20MB')
return
}
const row = form.transfers[index]
if (!row) return
if (!Array.isArray(row.attachments)) row.attachments = []
const tempId = `temp_${Date.now()}_${Math.random().toString(16).slice(2)}`
row.attachments.push({
id: tempId,
fileName: file.name,
fileUrl: '',
_uploading: true
})
const idx = row.attachments.length - 1
try {
const fd = new FormData()
fd.append('folder', 'presign-transfer')
fd.append('file', file)
const res: any = await fetchUploadAttachment(fd)
const data = res?.data || res
row.attachments[idx] = {
id: data?.id || tempId,
fileName: data?.fileName || file.name,
fileUrl: data?.fileUrl || data?.url || '',
_uploading: false
}
} catch {
ElMessage.error('文件上传失败')
row.attachments.splice(idx, 1)
}
}
function removeTransferAttachment(rowIndex: number, fileIndex: number) {
const row = form.transfers[rowIndex]
if (!row?.attachments) return
row.attachments.splice(fileIndex, 1)
}
function applyDefaultCollector() {
const stub = userStubFromStore()
if (!stub) {
form.collectorUserId = ''
collectorUserOptions.value = []
return
}
form.collectorUserId = stub.id
collectorUserOptions.value = [stub]
}
function resetForm() {
form.orderDate = todayYmd()
form.protocolNo = ''
form.motherName = ''
form.expectedDeliveryDate = ''
form.regionId = ''
form.hospitalId = ''
form.collectorUserId = ''
form.regionManagerUserId = ''
form.sampleCollectTime = ''
form.payMode = 'dj'
form.contractAmount = null
form.orderStatus = 'active'
form.sampleTypeIds = []
form.remark = ''
form.transfers = []
form.confirmedDepositAmount = 0
collectorUserOptions.value = []
managerUserOptions.value = []
applyDefaultCollector()
}
async function remoteSearchCollectorUsers(keyword: string) {
collectorLoading.value = true
try {
const res = await fetchUserList({
page: 1,
pageSize: 50,
keyword: (keyword || '').trim() || undefined,
status: '1'
})
let list = res.list || []
const cur = form.collectorUserId
if (cur && !list.some((x) => x.id === cur)) {
const keep = collectorUserOptions.value.find((x) => x.id === cur)
if (keep) list = [keep, ...list]
}
collectorUserOptions.value = list
} catch {
collectorUserOptions.value = []
} finally {
collectorLoading.value = false
}
}
async function remoteSearchManagerUsers(keyword: string) {
managerLoading.value = true
try {
const res = await fetchUserList({
page: 1,
pageSize: 50,
keyword: (keyword || '').trim() || undefined,
status: '1'
})
let list = res.list || []
const cur = form.regionManagerUserId
if (cur && !list.some((x) => x.id === cur)) {
const keep = managerUserOptions.value.find((x) => x.id === cur)
if (keep) list = [keep, ...list]
}
managerUserOptions.value = list
} catch {
managerUserOptions.value = []
} finally {
managerLoading.value = false
}
}
function onCollectorUserChange(val: string | null) {
form.collectorUserId = val || ''
}
function onManagerUserChange(val: string | null) {
form.regionManagerUserId = val || ''
}
async function applyDefaultRegionManager(regionId: string) {
if (!regionId) {
form.regionManagerUserId = ''
managerUserOptions.value = []
return
}
try {
const res: any = await fetchRegionStaffList(regionId, { page: 1, pageSize: 100 })
const list = (res?.list || []) as Array<Record<string, any>>
const leader = list.find((x) => x.isLeader) || list[0]
if (!leader?.userId) {
form.regionManagerUserId = ''
return
}
const stub = makeUserStub(
String(leader.userId),
String(leader.nickName || leader.userName || '')
)
form.regionManagerUserId = stub.id
if (!managerUserOptions.value.some((x) => x.id === stub.id)) {
managerUserOptions.value = [stub, ...managerUserOptions.value]
}
} catch {
/* 地区无人员配置时留空,可手工选择 */
}
}
function addTransfer() {
form.transfers.push({
transferType: '',
transferAmount: null,
transferTime: '',
payStatus: 'dfj',
remark: '',
financeRemark: '',
attachments: [] as TransferAttachment[]
})
}
async function loadLookups() {
await loadTransferTypeDict()
transferTypeOptions.value = getTransferTypeOptions()
const [types, regions] = await Promise.all([
fetchCellSampleTypeList({ page: 1, pageSize: 100 }),
fetchCollectionRegionList({ page: 1, pageSize: 100 })
])
sampleTypeOptions.value = (types as any)?.list || (types as any) || []
regionOptions.value = (regions as any)?.list || (regions as any) || []
}
async function loadHospitals() {
hospitalOptions.value = []
if (!form.regionId) return
const res: any = await fetchHospitalList(form.regionId, { page: 1, pageSize: 100 })
hospitalOptions.value = res?.list || []
}
async function onRegionChange() {
form.hospitalId = ''
await Promise.all([loadHospitals(), applyDefaultRegionManager(form.regionId)])
}
async function loadDetail() {
if (!props.orderId) return
loading.value = true
try {
const data: any = await fetchPresignOrderDetail(props.orderId)
form.orderDate = String(data.orderDate || '')
form.protocolNo = String(data.protocolNo || '')
form.motherName = String(data.motherName || '')
form.expectedDeliveryDate = String(data.expectedDeliveryDate || '')
form.regionId = String(data.regionId || '')
form.hospitalId = String(data.hospitalId || '')
form.collectorUserId = String(data.collectorUserId || '')
form.regionManagerUserId = String(data.regionManagerUserId || '')
form.sampleCollectTime = String(data.sampleCollectTime || '')
form.payMode = String(data.payMode || 'dj')
form.contractAmount = data.contractAmount ?? null
form.orderStatus = String(data.orderStatus || 'active')
form.sampleTypeIds = Array.isArray(data.sampleTypeIds) ? [...data.sampleTypeIds] : []
form.remark = String(data.remark || '')
form.transfers = Array.isArray(data.transfers)
? data.transfers.map((t: Record<string, any>) => normalizeTransfer(t))
: []
form.confirmedDepositAmount = Number(data.confirmedDepositAmount || 0)
if (form.collectorUserId) {
collectorUserOptions.value = [
makeUserStub(form.collectorUserId, String(data.collectorUserName || ''))
]
}
if (form.regionManagerUserId) {
managerUserOptions.value = [
makeUserStub(form.regionManagerUserId, String(data.regionManagerUserName || ''))
]
}
if (form.regionId) await loadHospitals()
} finally {
loading.value = false
}
}
async function submitTransfer(tr: Record<string, any>) {
if (!props.orderId || !tr.id) {
ElMessage.warning('请先保存订单后再提交财务确认')
return
}
if (tr.payStatus === 'dfj') {
await fetchUpdatePresignTransfer(String(tr.id), {
transferType: tr.transferType,
transferAmount: tr.transferAmount,
transferTime: tr.transferTime || undefined,
transferAttachments: attachmentsToUrls(tr.attachments) || undefined,
remark: tr.remark || undefined
})
}
await fetchSubmitPresignTransferFinance(String(tr.id))
tr.payStatus = 'jfqrz'
ElMessage.success('已提交财务确认')
}
async function withdrawTransfer(tr: Record<string, any>) {
if (!tr.id) return
await ElMessageBox.confirm(
'确认撤回该笔意向金的财务确认申请?撤回后可修改并重新提交。',
'撤回',
{ type: 'warning' }
)
await fetchWithdrawPresignTransfer(String(tr.id))
tr.payStatus = 'dfj'
ElMessage.success('已撤回')
}
async function handleSave() {
if (!form.motherName.trim()) {
ElMessage.warning('请填写母亲姓名')
return
}
if (!form.sampleTypeIds.length) {
ElMessage.warning('请至少选择一个样本类型')
return
}
saving.value = true
try {
const payload: Record<string, unknown> = {
orderDate: form.orderDate || undefined,
protocolNo: form.protocolNo || undefined,
motherName: form.motherName,
expectedDeliveryDate: form.expectedDeliveryDate || undefined,
regionId: form.regionId || undefined,
hospitalId: form.hospitalId || undefined,
sampleCollectTime: form.sampleCollectTime || undefined,
collectorUserId: form.collectorUserId || undefined,
regionManagerUserId: form.regionManagerUserId || undefined,
payMode: form.payMode || undefined,
contractAmount: form.contractAmount ?? undefined,
orderStatus: form.orderStatus || 'active',
sampleTypeIds: form.sampleTypeIds,
remark: form.remark || undefined
}
if (props.orderId) {
await fetchUpdatePresignOrder(props.orderId, payload)
for (const tr of form.transfers) {
if (!tr.id && tr.payStatus === 'dfj') {
await fetchCreatePresignTransfer(props.orderId, {
transferType: tr.transferType,
transferAmount: tr.transferAmount,
transferTime: tr.transferTime || undefined,
transferAttachments: attachmentsToUrls(tr.attachments) || undefined,
remark: tr.remark || undefined
})
} else if (tr.id && tr.payStatus === 'dfj') {
await fetchUpdatePresignTransfer(String(tr.id), {
transferType: tr.transferType,
transferAmount: tr.transferAmount,
transferTime: tr.transferTime || undefined,
transferAttachments: attachmentsToUrls(tr.attachments) || undefined,
remark: tr.remark || undefined
})
}
}
} else {
payload.transfers = form.transfers
.filter((t) => t.payStatus === 'dfj' || !t.payStatus)
.map((t) => ({
transferType: t.transferType,
transferAmount: t.transferAmount,
transferTime: t.transferTime || undefined,
transferAttachments: attachmentsToUrls(t.attachments) || undefined,
remark: t.remark || undefined
}))
await fetchCreatePresignOrder(payload)
}
emit('update:modelValue', false)
emit('saved')
} finally {
saving.value = false
}
}
watch(
() => props.modelValue,
async (open) => {
if (!open) return
resetForm()
await loadLookups()
if (props.orderId) await loadDetail()
}
)
</script>
<style scoped lang="scss">
.section-subtitle {
margin: 16px 0 8px;
font-size: 13px;
font-weight: 600;
color: var(--mint-text-secondary, #475569);
}
.transfer-toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 12px;
margin-bottom: 10px;
}
.deposit-hint {
font-size: 12px;
color: #047857;
}
.transfer-table {
width: 100%;
:deep(.el-date-editor) {
width: 100%;
}
}
.muted-dash {
color: var(--mint-text-muted, #94a3b8);
}
.attachment-upload {
width: 100%;
}
.attachment-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: flex-start;
}
.upload-card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 88px;
height: 72px;
cursor: pointer;
border: 1px dashed #a7f3d0;
border-radius: 8px;
background: #ecfdf5;
}
.upload-card-text {
margin-top: 4px;
font-size: 11px;
color: #047857;
}
.attachment-item {
width: 88px;
overflow: hidden;
background: #fff;
border: 1px solid #e2e8f0;
border-radius: 8px;
&.is-uploading {
opacity: 0.6;
}
}
.attachment-preview {
display: flex;
align-items: center;
justify-content: center;
width: 88px;
height: 52px;
background: #f8fafc;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.attachment-preview__icon {
color: #64748b;
}
.attachment-footer {
display: flex;
gap: 4px;
align-items: center;
padding: 4px 6px;
}
.attachment-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 11px;
}
.action-btn {
flex-shrink: 0;
cursor: pointer;
color: #94a3b8;
&:hover {
color: #dc2626;
}
}
</style>