This commit is contained in:
zlwu committed 2026-06-15 11:43:16 +08:00
1 parent 427bcb1551
commit 31b04575e2
247 files changed
+39284 -20367

No files matched your search

@@ -3,9 +3,11 @@
<el-dialog
:model-value="modelValue"
:title="dialogTitle"
width="1380px"
width="1600px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="protocol-dialog-mask"
destroy-on-close
:close-on-click-modal="false"
>
@@ -82,12 +84,11 @@
/>
</el-select>
</el-form-item>
</div>
<div class="form-grid form-grid--col2 basic-remark-attach-row">
<el-form-item label="备注">
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="可选" />
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可选" />
</el-form-item>
</div>
<div class="form-grid form-grid--attachment">
<el-form-item label="协议附件">
<div class="attachment-upload">
<div class="attachment-list">
@@ -169,7 +170,7 @@
<div class="section-title">
<div class="section-title-left"><h3>采集信息</h3></div>
</div>
<div class="form-grid">
<div class="form-grid form-grid--col6">
<el-form-item label="采集地区">
<el-select
v-model="formData.regionId"
@@ -271,12 +272,22 @@
<SampleTransportFormSection
v-model:samples="formData.samples"
:sample-type-options="sampleTypeOptions"
:sample-type-options-loading="sampleTypeLoading"
:sample-code-options="sampleCodeOptions"
:sample-code-options-loading="sampleCodeLoading"
:unit-options="unitOptions"
:unit-options-loading="unitLoading"
:site-options="siteOptions"
:site-options-loading="siteLoading"
:express-company-options="expressCompanyOptions"
:express-company-options-loading="expressCompanyLoading"
:show-price="true"
@price-change="onSamplePriceChange"
@search-sample-code="remoteSearchSampleCodes"
@search-sample-type="remoteSearchSampleTypes"
@search-unit="remoteSearchUnits"
@search-site="remoteSearchSites"
@search-express-company="remoteSearchExpressCompanies"
/>
</div>
</div>
@@ -486,7 +497,7 @@
<!-- 缴费主表 -->
<div class="payment-main-card">
<div class="section-subtitle">缴费主表</div>
<div class="form-grid form-grid--col5">
<div class="form-grid form-grid--col6">
<el-form-item label="缴费模式">
<el-radio-group v-model="formData.payMode" @change="onPayModeChange">
<el-radio label="dj">趸交</el-radio>
@@ -547,68 +558,52 @@
@change="handleFqParamsChange"
/>
</el-form-item>
<div class="installment-advance-ratios-row form-col-span-2">
<el-form-item label="应缴费提前(天)" class="installment-advance-field">
<el-input
v-model="formData.installmentAdvanceDays"
type="number"
:min="0"
:max="365"
step="1"
style="width: 120px"
@change="handleFqParamsChange"
/>
</el-form-item>
<el-form-item label="缴费比例(%)" class="installment-ratios-field">
<div class="installment-ratios-panel">
<div class="installment-ratios-bar" aria-hidden="true">
<div
v-for="(ratio, rIdx) in formData.installmentRatios"
:key="'bar-' + rIdx"
class="installment-ratios-bar-seg"
:class="{ 'is-auto': rIdx === formData.installmentRatios.length - 1 }"
:style="{ flex: Math.max(Number(ratio) || 0, 0.001) }"
:title="`第${rIdx + 1}期 ${ratio}%`"
/>
</div>
<div class="installment-ratios-cells">
<div
v-for="(_ratio, rIdx) in formData.installmentRatios"
:key="rIdx"
class="installment-ratio-cell"
:class="{ 'is-auto': rIdx === formData.installmentRatios.length - 1 }"
>
<span class="installment-ratio-cell-no">{{ rIdx + 1 }}期</span>
<el-input
v-model="formData.installmentRatios[rIdx]"
type="number"
:min="0"
:max="100"
step="any"
:disabled="rIdx === formData.installmentRatios.length - 1"
@change="onInstallmentRatioChange(rIdx)"
>
<template #suffix>%</template>
</el-input>
</div>
</div>
<div class="installment-ratios-foot">
<span class="form-label-tip">末期为自动补齐</span>
<span
class="installment-ratios-sum"
:class="{ 'is-error': !!installmentRatiosError }"
>
合计 {{ installmentRatiosSum }}%
</span>
</div>
<div v-if="installmentRatiosError" class="form-tip form-tip--error">
{{ installmentRatiosError }}
</div>
</div>
</el-form-item>
</div>
<el-form-item label="应缴费提前(天)">
<el-input
v-model="formData.installmentAdvanceDays"
type="number"
:min="0"
:max="365"
step="1"
style="width: 100%"
@change="handleFqParamsChange"
/>
</el-form-item>
</template>
</div>
<div v-if="formData.payMode === 'fq'" class="installment-amounts-row">
<el-form-item label="分期金额(元)" class="installment-amounts-field">
<div class="installment-amounts-cells">
<div
v-for="(_amount, rIdx) in formData.installmentRatios"
:key="rIdx"
class="installment-amount-cell"
>
<span class="installment-amount-cell-no">{{ rIdx + 1 }}期</span>
<AmountInput
v-model="formData.installmentRatios[rIdx]"
:min="0"
placeholder="金额"
@change="onInstallmentAmountChange(rIdx)"
/>
</div>
</div>
</el-form-item>
<div class="installment-amounts-summary-row">
<span
class="installment-amounts-sum"
:class="{ 'is-error': !!installmentAmountsError }"
>
合计 {{ fmtMoney(installmentAmountsSum) }}
<template v-if="formData.contractAmount != null">
/ 合同 {{ fmtMoney(formData.contractAmount) }}
</template>
</span>
<div v-if="installmentAmountsError" class="form-tip form-tip--error">
{{ installmentAmountsError }}
</div>
</div>
</div>
</div>
<!-- 缴费明细 -->
@@ -844,7 +839,14 @@
</el-dialog>
<!-- ========== 文件预览弹窗 ========== -->
<el-dialog v-model="previewVisible" title="文件预览" width="800px" align-center destroy-on-close>
<el-dialog
v-model="previewVisible"
title="文件预览"
width="800px"
align-center
append-to-body
destroy-on-close
>
<div v-if="previewType === 'image'" class="preview-container">
<img
:src="previewUrl"
@@ -900,16 +902,14 @@
import { fetchEthnicList, type EthnicOption } from '@/api/ethnic'
import { useUserStore } from '@/store/modules/user'
import {
calcInstallmentDueAmountsByRatios,
calcUnpaidAmountsByRatios,
DEFAULT_INSTALLMENT_ADVANCE_DAYS,
DEFAULT_INSTALLMENT_RATIOS_6,
defaultInstallmentRatios,
finalizeInstallmentRatios,
isInstallmentRatiosValid,
resizeInstallmentRatios,
syncFqPaymentDetailRowCount
} from '@/utils/protocol-payment-installment'
DEFAULT_INSTALLMENT_ADVANCE_DAYS,
isInstallmentAmountsValid,
normalizeInstallmentAmounts,
resizeInstallmentAmounts,
scaleInstallmentAmountsToContract,
sumInstallmentAmounts,
syncFqPaymentDetailRowCount
} from '@/utils/protocol-payment-installment'
import {
applyPayModeSwitch,
canAddPaymentDetail,
@@ -955,7 +955,7 @@
installmentIntervalMonths: null,
installmentStartDate: '',
installmentAdvanceDays: DEFAULT_INSTALLMENT_ADVANCE_DAYS,
installmentRatios: [...DEFAULT_INSTALLMENT_RATIOS_6]
installmentRatios: [] as number[]
})
const customerId = ref<string | null>(null)
@@ -997,6 +997,10 @@
const unitOptions = ref<Array<{ id: string; unitName: string; unitCode: string }>>([])
const sampleCodeOptions = ref<Api.BizSampleCode.SampleCodeItem[]>([])
const sampleCodeLoading = ref(false)
const sampleTypeLoading = ref(false)
const unitLoading = ref(false)
const siteLoading = ref(false)
const expressCompanyLoading = ref(false)
const expressCompanyOptions = ref<Api.ExpressCompany.ExpressCompanyItem[]>([])
const siteOptions = ref<Api.Site.SiteItem[]>([])
let sampleRowSeq = 0
@@ -1194,7 +1198,7 @@
lumpSumDeadline: '',
installmentStartDate: '',
installmentAdvanceDays: DEFAULT_INSTALLMENT_ADVANCE_DAYS as number | null,
installmentRatios: [...DEFAULT_INSTALLMENT_RATIOS_6] as number[],
installmentRatios: [] as number[] as number[],
// Payment Details
paymentDetails: [] as Array<Record<string, any>>
})
@@ -1248,7 +1252,7 @@
lumpSumDeadline: '',
installmentStartDate: '',
installmentAdvanceDays: DEFAULT_INSTALLMENT_ADVANCE_DAYS,
installmentRatios: [...DEFAULT_INSTALLMENT_RATIOS_6],
installmentRatios: [] as number[],
paymentDetails: []
})
customerId.value = null
@@ -1262,6 +1266,11 @@
salesSnapshot.value = null
regionOptions.value = []
hospitalOptions.value = []
sampleTypeOptions.value = []
unitOptions.value = []
sampleCodeOptions.value = []
siteOptions.value = []
expressCompanyOptions.value = []
activeFormTab.value = 'basic'
lastConfirmedPayMode.value = 'dj'
lastConfirmedFqParams.value = {
@@ -1269,7 +1278,7 @@
installmentIntervalMonths: null,
installmentStartDate: '',
installmentAdvanceDays: DEFAULT_INSTALLMENT_ADVANCE_DAYS,
installmentRatios: [...DEFAULT_INSTALLMENT_RATIOS_6]
installmentRatios: [] as number[]
}
}
@@ -1377,13 +1386,11 @@
formData.installmentAdvanceDays = DEFAULT_INSTALLMENT_ADVANCE_DAYS
}
const count = toIntOrUndefined(formData.installmentCount) || 6
if (reset || !formData.installmentRatios?.length) {
formData.installmentRatios = defaultInstallmentRatios(count)
} else {
formData.installmentRatios = finalizeInstallmentRatios(
resizeInstallmentRatios(formData.installmentRatios, count)
)
}
formData.installmentRatios = normalizeInstallmentAmounts(
formData.installmentRatios || [],
formData.contractAmount,
count
)
}
function toIntOrUndefined(val: unknown): number | undefined {
@@ -1428,7 +1435,7 @@
installmentIntervalMonths: toFloatOrUndefined(formData.installmentIntervalMonths) ?? null,
installmentStartDate: formData.installmentStartDate || '',
installmentAdvanceDays: toIntOrUndefined(formData.installmentAdvanceDays) ?? null,
installmentRatios: finalizeInstallmentRatios([...(formData.installmentRatios || [])])
installmentRatios: [...(formData.installmentRatios || [])].map((v) => Number(v || 0))
}
}
@@ -1584,7 +1591,6 @@
resetForm()
formData.motherEthnic = DEFAULT_MOTHER_ETHNIC
formData.registerDate = new Date().toISOString().slice(0, 10).replace(/-/g, '')
loadSampleOptions()
void loadEthnicOptions()
void remoteSearchCustomers('')
@@ -1686,12 +1692,18 @@
formData.installmentStartDate = String(pay.installmentStartDate || '')
formData.installmentAdvanceDays =
pay.installmentAdvanceDays ?? DEFAULT_INSTALLMENT_ADVANCE_DAYS
const loadedRatios = Array.isArray(pay.installmentRatios) ? pay.installmentRatios : []
if (loadedRatios.length) {
formData.installmentRatios = loadedRatios.map((v: unknown) => Number(v))
const loadedAmounts = Array.isArray(pay.installmentRatios) ? pay.installmentRatios : []
if (loadedAmounts.length) {
formData.installmentRatios = loadedAmounts.map((v: unknown) => Number(v))
}
if (formData.payMode === 'fq') {
applyFqPaymentDefaults()
const count = toIntOrUndefined(formData.installmentCount) || 6
formData.installmentRatios = normalizeInstallmentAmounts(
formData.installmentRatios,
formData.contractAmount,
count
)
syncLastConfirmedFqParams()
}
}
@@ -1741,8 +1753,7 @@
dialogFormLoading.value = true
try {
const [, , bundle] = await Promise.all([
loadSampleOptions(),
const [, bundle] = await Promise.all([
loadEthnicOptions(),
fetchCollectionProtocolDetail(String(row.id)).catch(() => null)
])
@@ -1779,7 +1790,13 @@
}
await Promise.all(hydrateTasks)
if (formData.samples.length) {
ensureSampleCodesInOptions(formData.samples)
await Promise.all([
remoteSearchSampleTypes(''),
remoteSearchSites(''),
remoteSearchUnits(''),
remoteSearchSampleCodes('')
])
hydrateSampleLookupOptions()
}
} catch {
/* 保留列表行已填基础字段 */
@@ -1792,13 +1809,13 @@
if (!formRef.value) return
await formRef.value.validate()
if (formData.payMode === 'fq') {
if (installmentRatiosError.value) {
await ElMessageBox.alert(installmentRatiosError.value, '缴费比例无效', { type: 'warning' })
if (installmentAmountsError.value) {
await ElMessageBox.alert(installmentAmountsError.value, '分期金额无效', { type: 'warning' })
return
}
const ratios = finalizeInstallmentRatios([...(formData.installmentRatios || [])])
if (!isInstallmentRatiosValid(ratios)) {
await ElMessageBox.alert('缴费比例之和须等于 100%', '缴费比例无效', { type: 'warning' })
const amounts = (formData.installmentRatios || []).map((v) => Number(v || 0))
if (!isInstallmentAmountsValid(amounts, formData.contractAmount)) {
await ElMessageBox.alert('分期金额合计须等于合同金额', '分期金额无效', { type: 'warning' })
return
}
}
@@ -1883,7 +1900,7 @@
installmentAdvanceDays: toIntOrUndefined(formData.installmentAdvanceDays),
installmentRatios:
formData.payMode === 'fq'
? finalizeInstallmentRatios([...(formData.installmentRatios || [])])
? (formData.installmentRatios || []).map((v) => Number(v || 0))
: undefined
},
paymentDetails: formData.paymentDetails.map(
@@ -2116,6 +2133,89 @@
sampleCodeOptions.value = merged
}
function ensureSampleTypesInOptions(samples: Array<Record<string, any>>) {
const merged = [...sampleTypeOptions.value]
for (const s of samples) {
const id = String(s.sampleTypeId || '')
if (!id) continue
const name = String(s.sampleTypeName || '').trim()
const existing = merged.find((x) => x.id === id)
if (existing) {
if (name && (!existing.typeName || existing.typeName === existing.id)) {
existing.typeName = name
}
continue
}
merged.push({
id,
typeCode: '',
typeName: name || id,
storageYears: s.storageYears ?? null,
storagePrice: s.originalPrice ?? null
})
}
sampleTypeOptions.value = merged
}
function ensureUnitsInOptions(samples: Array<Record<string, any>>) {
const merged = [...unitOptions.value]
for (const s of samples) {
const id = String(s.unitId || '')
if (!id) continue
const name = String(s.unitName || '').trim()
const existing = merged.find((x) => x.id === id)
if (existing) {
if (name && (!existing.unitName || existing.unitName === existing.id)) {
existing.unitName = name
}
continue
}
merged.push({ id, unitName: name || id, unitCode: String(s.unitCode || '') })
}
unitOptions.value = merged
}
function ensureSitesInOptions(samples: Array<Record<string, any>>) {
const merged = [...siteOptions.value]
for (const s of samples) {
const id = String(s.transport?.destSiteId || '')
if (!id) continue
const name = String(s.transport?.destSiteName || '').trim()
const existing = merged.find((x) => x.id === id)
if (existing) {
if (name && (!existing.siteName || existing.siteName === existing.id)) {
existing.siteName = name
}
continue
}
merged.push({
id,
siteCode: '',
siteName: name || id,
siteType: '',
status: '',
address: '',
contactName: '',
contactPhone: '',
businessScope: '',
siteImage: '',
tenantNo: '',
orderNum: 0,
remark: '',
createTime: '',
updateTime: ''
})
}
siteOptions.value = merged
}
function hydrateSampleLookupOptions() {
ensureSampleTypesInOptions(formData.samples)
ensureSampleCodesInOptions(formData.samples)
ensureUnitsInOptions(formData.samples)
ensureSitesInOptions(formData.samples)
}
/* ====== 客户检索(母亲信息) ====== */
function formatCustomerLabel(c: { customerName: string; phoneNo: string; idCardNo: string }) {
const parts = [c.customerName || '']
@@ -2248,23 +2348,91 @@
formData.salesUserId = u.id
}
/* ====== 样本类型/单位选项 ====== */
async function loadSampleOptions() {
async function remoteSearchSampleCodes(query: string) {
sampleCodeLoading.value = true
try {
const [stRes, uRes, scRes, ecRes, siteRes] = await Promise.all([
fetchCellSampleTypeList({ page: 1, pageSize: 100 }),
fetchItemUnitList({ page: 1, pageSize: 100 }),
fetchSampleCodeList({ page: 1, pageSize: 100 }),
fetchExpressCompanyList({ page: 1, pageSize: 100 }),
fetchSiteList({ page: 1, pageSize: 100 })
])
sampleTypeOptions.value = (stRes.list || []) as any
unitOptions.value = (uRes.list || []) as any
sampleCodeOptions.value = (scRes.list || []) as any
expressCompanyOptions.value = ecRes.list || []
siteOptions.value = siteRes.list || []
const res = await fetchSampleCodeList({
page: 1,
pageSize: 100,
keyword: query?.trim() || undefined
})
sampleCodeOptions.value = (res.list || []) as Api.BizSampleCode.SampleCodeItem[]
ensureSampleCodesInOptions(formData.samples)
} catch {
/* ignore */
sampleCodeOptions.value = []
ensureSampleCodesInOptions(formData.samples)
} finally {
sampleCodeLoading.value = false
}
}
async function remoteSearchSampleTypes(query: string) {
sampleTypeLoading.value = true
try {
const res = await fetchCellSampleTypeList({
page: 1,
pageSize: 100,
keyword: query?.trim() || undefined
})
sampleTypeOptions.value = (res.list || []) as any
ensureSampleTypesInOptions(formData.samples)
} catch {
sampleTypeOptions.value = []
ensureSampleTypesInOptions(formData.samples)
} finally {
sampleTypeLoading.value = false
}
}
async function remoteSearchUnits(query: string) {
unitLoading.value = true
try {
const res = await fetchItemUnitList({
page: 1,
pageSize: 100,
keyword: query?.trim() || undefined
})
unitOptions.value = (res.list || []) as any
ensureUnitsInOptions(formData.samples)
} catch {
unitOptions.value = []
ensureUnitsInOptions(formData.samples)
} finally {
unitLoading.value = false
}
}
async function remoteSearchSites(query: string) {
siteLoading.value = true
try {
const res = await fetchSiteList({
page: 1,
pageSize: 100,
keyword: query?.trim() || undefined
})
siteOptions.value = res.list || []
ensureSitesInOptions(formData.samples)
} catch {
siteOptions.value = []
ensureSitesInOptions(formData.samples)
} finally {
siteLoading.value = false
}
}
async function remoteSearchExpressCompanies(query: string) {
expressCompanyLoading.value = true
try {
const res = await fetchExpressCompanyList({
page: 1,
pageSize: 100,
keyword: query?.trim() || undefined
})
expressCompanyOptions.value = res.list || []
} catch {
expressCompanyOptions.value = []
} finally {
expressCompanyLoading.value = false
}
}
@@ -2341,33 +2509,32 @@
function onInstallmentCountChange() {
const count = toIntOrUndefined(formData.installmentCount) || 0
if (count > 0) {
formData.installmentRatios = finalizeInstallmentRatios(
resizeInstallmentRatios(formData.installmentRatios || [], count)
formData.installmentRatios = resizeInstallmentAmounts(
formData.installmentRatios || [],
count,
formData.contractAmount
)
}
handleFqParamsChange()
}
function onInstallmentRatioChange(changedIdx: number) {
if (changedIdx === formData.installmentRatios.length - 1) return
formData.installmentRatios = finalizeInstallmentRatios([...formData.installmentRatios])
function onInstallmentAmountChange(_changedIdx: number) {
syncPaymentDueAmounts()
handleFqParamsChange()
}
const installmentRatiosError = computed(() => {
const ratios = formData.installmentRatios || []
if (!ratios.length) return ''
const head = ratios.slice(0, -1)
const sum = head.reduce((s, v) => s + Number(v || 0), 0)
if (sum > 100 + 0.001) return '前 N−1 期比例之和不能超过 100%'
const installmentAmountsError = computed(() => {
const amounts = formData.installmentRatios || []
if (!amounts.length || formData.contractAmount == null) return ''
if (amounts.some((v) => Number(v) < 0)) return '分期金额不能为负数'
const sum = sumInstallmentAmounts(amounts)
if (Math.abs(sum - Number(formData.contractAmount)) > 0.01) {
return `分期金额合计须等于合同金额(${fmtMoney(formData.contractAmount)})`
}
return ''
})
const installmentRatiosSum = computed(() => {
const total = (formData.installmentRatios || []).reduce((s, v) => s + Number(v || 0), 0)
return Math.round(total * 100) / 100
})
const installmentAmountsSum = computed(() => sumInstallmentAmounts(formData.installmentRatios || []))
/** 按缴费模式同步各明细应缴费金额(跳过已锁定明细) */
function syncPaymentDueAmounts() {
@@ -2388,27 +2555,16 @@
if (formData.payMode === 'fq') {
const count = formData.installmentCount || formData.paymentDetails.length
if (!count) return
const ratios = finalizeInstallmentRatios(formData.installmentRatios || [])
const amounts = normalizeInstallmentAmounts(
formData.installmentRatios || [],
contract,
Number(count)
)
const paidRows = formData.paymentDetails.filter((d) => String(d.payStatus) === 'yjf')
let amounts: number[]
if (paidRows.length > 0 && formData.paymentDetails.some((d) => isPayDetailLocked(d))) {
const paidSum = sumPaidAmount(formData.paymentDetails)
const remaining = Math.round((Number(contract) - paidSum) * 100) / 100
const unpaidIndices = Array.from({ length: Number(count) }, (_, i) => i).filter(
(i) => !paidRows.some((d) => Number(d.installmentNo) === i + 1)
)
const unpaidAmounts = calcUnpaidAmountsByRatios(remaining, ratios, unpaidIndices)
amounts = Array(Number(count)).fill(0)
unpaidIndices.forEach((idx, j) => {
amounts[idx] = unpaidAmounts[j] ?? 0
})
paidRows.forEach((d) => {
const no = Number(d.installmentNo)
if (no >= 1 && no <= Number(count)) amounts[no - 1] = Number(d.dueAmount) || 0
})
} else {
amounts = calcInstallmentDueAmountsByRatios(Number(contract), ratios)
}
paidRows.forEach((d) => {
const no = Number(d.installmentNo)
if (no >= 1 && no <= Number(count)) amounts[no - 1] = Number(d.dueAmount) || 0
})
formData.paymentDetails.forEach((d, i) => {
if (isPayDetailLocked(d)) return
if (amounts[i] !== undefined) d.dueAmount = amounts[i]
@@ -2422,7 +2578,14 @@
if (s.sampleStatus === 'yzf') return sum
return sum + (Number(s.price) || 0)
}, 0)
formData.contractAmount = Math.round(total * 100) / 100
const nextContract = Math.round(total * 100) / 100
if (formData.payMode === 'fq' && (formData.installmentRatios || []).length) {
formData.installmentRatios = scaleInstallmentAmountsToContract(
formData.installmentRatios,
nextContract
)
}
formData.contractAmount = nextContract
syncPaymentDueAmounts()
}
@@ -2756,15 +2919,20 @@
grid-template-columns: repeat(5, minmax(0, 1fr));
}
&--col6 {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
&--attachment {
margin-top: 16px;
grid-template-columns: 1fr;
}
:deep(.el-form-item) {
margin-bottom: 0;
}
}
.basic-remark-attach-row {
margin-top: 16px;
}
.form-col-span-2 {
grid-column: 1 / -1;
}
@@ -3143,13 +3311,10 @@
grid-template-columns: repeat(2, minmax(0, 1fr));
}
&--col5 {
&--col5,
&--col6 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
&--col2.basic-remark-attach-row {
grid-template-columns: 1fr;
}
}
.client-sub-cards {
@@ -3157,106 +3322,48 @@
}
}
.installment-advance-ratios-row {
display: flex;
align-items: flex-start;
gap: 24px;
flex-wrap: wrap;
.installment-amounts-row {
margin-top: 16px;
}
.installment-advance-field {
flex: 0 0 auto;
.installment-amounts-field.el-form-item {
margin-bottom: 0;
}
.installment-ratios-field {
flex: 1 1 360px;
min-width: 0;
.installment-amounts-summary-row {
margin-top: 4px;
}
.installment-ratios-panel {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 14px;
border: 1px solid #c5e0d4;
border-radius: 8px;
background: #f8fafc;
}
.installment-ratios-bar {
display: flex;
height: 8px;
overflow: hidden;
border-radius: 4px;
background: #e2e8f0;
}
.installment-ratios-bar-seg {
background: #047857;
min-width: 2px;
transition: flex 0.2s ease;
&.is-auto {
background: #6bb99a;
}
& + & {
border-left: 1px solid rgba(255, 255, 255, 0.6);
}
}
.installment-ratios-cells {
.installment-amounts-cells {
display: flex;
flex-wrap: wrap;
gap: 8px;
gap: 12px 16px;
}
.installment-ratio-cell {
.installment-amount-cell {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1 1 72px;
min-width: 72px;
max-width: 96px;
:deep(.el-input__wrapper) {
padding-left: 8px;
padding-right: 4px;
}
:deep(.el-input__suffix) {
color: var(--mint-text-muted, #64748b);
font-size: 12px;
}
&.is-auto :deep(.el-input__wrapper) {
background: #e8f4ee;
}
gap: 6px;
min-width: 120px;
flex: 1 1 120px;
max-width: 180px;
}
.installment-ratio-cell-no {
font-size: 11px;
color: var(--mint-text-muted, #64748b);
line-height: 1.2;
}
.installment-ratios-foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.installment-ratios-sum {
.installment-amount-cell-no {
font-size: 12px;
font-weight: 600;
color: #64748b;
}
.installment-amounts-sum {
font-size: 13px;
font-weight: 600;
color: #065f46;
font-weight: 500;
&.is-error {
color: #dc2626;
}
}
.form-tip--error {
margin-top: 6px;
color: #dc2626;