|
|
@@ -0,0 +1,569 @@
|
|
|
+<template>
|
|
|
+ <div class="sample-payment-panel">
|
|
|
+ <el-empty
|
|
|
+ v-if="!syncedPayments.length"
|
|
|
+ description="请先在「样本信息」中添加样本,再配置按样本缴费"
|
|
|
+ :image-size="48"
|
|
|
+ />
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-for="(pay, pIdx) in syncedPayments"
|
|
|
+ :key="pay.sampleCode || pIdx"
|
|
|
+ class="sample-pay-card"
|
|
|
+ >
|
|
|
+ <div class="sample-pay-card__head">
|
|
|
+ <div class="sample-pay-card__title">
|
|
|
+ {{ sampleTitle(pay) }}
|
|
|
+ <el-tag v-if="pay.sampleCode" size="small" type="info" class="ml-2">
|
|
|
+ {{ pay.sampleCode }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+ <el-tag :type="pay.payMode === 'fq' ? 'warning' : 'success'" size="small">
|
|
|
+ {{ pay.payMode === 'fq' ? '分期' : '趸交' }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-grid form-grid--col4">
|
|
|
+ <el-form-item label="缴费模式">
|
|
|
+ <el-select
|
|
|
+ :model-value="pay.payMode"
|
|
|
+ style="width: 100%"
|
|
|
+ @update:model-value="(v: string) => onPayModeChange(pIdx, v)"
|
|
|
+ >
|
|
|
+ <el-option label="趸交" value="dj" />
|
|
|
+ <el-option label="分期" value="fq" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="合同金额(元)">
|
|
|
+ <AmountInput
|
|
|
+ :model-value="pay.contractAmount"
|
|
|
+ :min="0"
|
|
|
+ style="width: 100%"
|
|
|
+ @update:model-value="(v: number | null) => onContractAmountChange(pIdx, v)"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <template v-if="pay.payMode === 'dj'">
|
|
|
+ <el-form-item label="趸交截止时间">
|
|
|
+ <el-date-picker
|
|
|
+ :model-value="pay.lumpSumDeadline"
|
|
|
+ type="date"
|
|
|
+ value-format="YYYYMMDD"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ placeholder="选择截止时间"
|
|
|
+ clearable
|
|
|
+ style="width: 100%"
|
|
|
+ @update:model-value="(v: string) => patchPay(pIdx, { lumpSumDeadline: v || '' })"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <el-form-item label="分期期数">
|
|
|
+ <el-input
|
|
|
+ :model-value="pay.installmentCount"
|
|
|
+ type="number"
|
|
|
+ :min="1"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="(v: string) => onFqFieldChange(pIdx, 'installmentCount', v)"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="每期间隔(月)">
|
|
|
+ <el-input
|
|
|
+ :model-value="pay.installmentIntervalMonths"
|
|
|
+ type="number"
|
|
|
+ :min="0.1"
|
|
|
+ step="any"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="(v: string) => onFqFieldChange(pIdx, 'installmentIntervalMonths', v)"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="分期开始时间">
|
|
|
+ <el-date-picker
|
|
|
+ :model-value="pay.installmentStartDate"
|
|
|
+ type="date"
|
|
|
+ value-format="YYYYMMDD"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ clearable
|
|
|
+ style="width: 100%"
|
|
|
+ @update:model-value="(v: string) => onFqStartDateChange(pIdx, v)"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="应缴费提前(天)">
|
|
|
+ <el-input
|
|
|
+ :model-value="pay.installmentAdvanceDays"
|
|
|
+ type="number"
|
|
|
+ :min="0"
|
|
|
+ :max="365"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="(v: string) => onFqFieldChange(pIdx, 'installmentAdvanceDays', v)"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="section-subtitle">应缴批次</div>
|
|
|
+ <div v-if="!(pay.schedules || []).length" class="muted-hint"
|
|
|
+ >暂无批次,请完善缴费参数后自动生成</div
|
|
|
+ >
|
|
|
+ <div v-for="(sch, sIdx) in pay.schedules || []" :key="sch.id || sIdx" class="schedule-block">
|
|
|
+ <div class="schedule-block__head">
|
|
|
+ <span
|
|
|
+ >第 {{ sch.scheduleNo || sIdx + 1 }} 期 · 应缴
|
|
|
+ {{ fmtMoney(sch.dueAmount) }}
|
|
|
+ <template v-if="sch.dueDate"> · {{ formatDueDate(sch.dueDate) }}</template>
|
|
|
+ </span>
|
|
|
+ <span class="schedule-arrears">
|
|
|
+ 已缴
|
|
|
+ {{
|
|
|
+ fmtMoney(schedulePaidArrears(Number(sch.dueAmount) || 0, sch.transfers || []).paid)
|
|
|
+ }}
|
|
|
+ · 欠缴
|
|
|
+ {{
|
|
|
+ fmtMoney(schedulePaidArrears(Number(sch.dueAmount) || 0, sch.transfers || []).arrears)
|
|
|
+ }}
|
|
|
+ </span>
|
|
|
+ <el-button link type="primary" size="small" @click="addTransfer(pIdx, sIdx)">
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
+ 添加转账
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ v-for="(tr, tIdx) in sch.transfers || []"
|
|
|
+ :key="tr.id || `${sIdx}-${tIdx}`"
|
|
|
+ class="transfer-row form-grid form-grid--col4"
|
|
|
+ >
|
|
|
+ <el-form-item label="转账方式">
|
|
|
+ <el-select
|
|
|
+ v-model="tr.transferType"
|
|
|
+ placeholder="请选择"
|
|
|
+ style="width: 100%"
|
|
|
+ :disabled="isTransferLocked(tr)"
|
|
|
+ @change="emitChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="opt in transferTypeOptions"
|
|
|
+ :key="opt.value"
|
|
|
+ :label="opt.label"
|
|
|
+ :value="opt.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="转账金额(元)">
|
|
|
+ <AmountInput
|
|
|
+ v-model="tr.transferAmount"
|
|
|
+ :min="0"
|
|
|
+ :disabled="isTransferLocked(tr)"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="emitChange"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="转账时间">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="tr.transferTime"
|
|
|
+ type="datetime"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ clearable
|
|
|
+ :disabled="isTransferLocked(tr)"
|
|
|
+ style="width: 100%"
|
|
|
+ @change="emitChange"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-tag :type="payStatusTagType(String(tr.payStatus || 'dfj'))" size="small">
|
|
|
+ {{ payStatusLabel(String(tr.payStatus || 'dfj')) }}
|
|
|
+ </el-tag>
|
|
|
+ <el-button
|
|
|
+ v-if="tr.id && tr.payStatus === 'dfj' && protocolId"
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ size="small"
|
|
|
+ style="margin-left: 8px"
|
|
|
+ :loading="submittingId === tr.id"
|
|
|
+ @click="submitTransfer(tr)"
|
|
|
+ >
|
|
|
+ 提交财务确认
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="!isTransferLocked(tr)"
|
|
|
+ link
|
|
|
+ type="danger"
|
|
|
+ size="small"
|
|
|
+ style="margin-left: 4px"
|
|
|
+ @click="removeTransfer(pIdx, sIdx, tIdx)"
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { computed, ref, watch } from 'vue'
|
|
|
+ import { Plus } 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 {
|
|
|
+ DEFAULT_INSTALLMENT_ADVANCE_DAYS,
|
|
|
+ normalizeInstallmentAmounts
|
|
|
+ } from '@/utils/protocol-payment-installment'
|
|
|
+ import { generateInitialFqDetails } from '@/utils/protocol-fq-params-change'
|
|
|
+ import {
|
|
|
+ parseInstallmentRatios,
|
|
|
+ resolvePerSamplePreset,
|
|
|
+ schedulePaidArrears
|
|
|
+ } from '@/utils/protocol-sample-payment'
|
|
|
+ import { fetchSubmitPaymentTransferFinance } from '@/api/protocol-sample-payment'
|
|
|
+
|
|
|
+ const props = defineProps<{
|
|
|
+ modelValue: Array<Record<string, any>>
|
|
|
+ samples: Array<Record<string, any>>
|
|
|
+ protocolId?: string
|
|
|
+ }>()
|
|
|
+
|
|
|
+ const emit = defineEmits<{
|
|
|
+ 'update:modelValue': [Array<Record<string, any>>]
|
|
|
+ }>()
|
|
|
+
|
|
|
+ const transferTypeOptions = ref<Api.Dict.DictDataOption[]>([])
|
|
|
+ const submittingId = ref('')
|
|
|
+
|
|
|
+ async function ensureTransferTypes() {
|
|
|
+ await loadTransferTypeDict()
|
|
|
+ transferTypeOptions.value = getTransferTypeOptions()
|
|
|
+ }
|
|
|
+ ensureTransferTypes()
|
|
|
+
|
|
|
+ function fmtMoney(v: unknown): string {
|
|
|
+ if (v == null || v === '') return '—'
|
|
|
+ return '¥' + formatMoneyDisplay(v)
|
|
|
+ }
|
|
|
+
|
|
|
+ function formatDueDate(v: string) {
|
|
|
+ if (!v || v.length !== 8) return v || ''
|
|
|
+ return `${v.slice(0, 4)}-${v.slice(4, 6)}-${v.slice(6, 8)}`
|
|
|
+ }
|
|
|
+
|
|
|
+ function isTransferLocked(tr: Record<string, any>) {
|
|
|
+ return tr.payStatus === 'jfqrz' || tr.payStatus === 'yjf'
|
|
|
+ }
|
|
|
+
|
|
|
+ function sampleTitle(pay: Record<string, any>) {
|
|
|
+ const sample = (props.samples || []).find(
|
|
|
+ (s) => String(s.sampleCode || '') === String(pay.sampleCode || '')
|
|
|
+ )
|
|
|
+ return String(sample?.sampleTypeName || sample?.sampleName || '样本') || '样本'
|
|
|
+ }
|
|
|
+
|
|
|
+ function emptyDjPayment(sample: Record<string, any>): Record<string, any> {
|
|
|
+ const preset = resolvePerSamplePreset('dj')
|
|
|
+ const amount = preset?.contractAmount ?? null
|
|
|
+ return {
|
|
|
+ sampleCode: String(sample.sampleCode || ''),
|
|
|
+ protocolSampleId: sample.id || '',
|
|
|
+ payMode: 'dj',
|
|
|
+ contractAmount: amount,
|
|
|
+ lumpSumDeadline: '',
|
|
|
+ installmentCount: null,
|
|
|
+ installmentIntervalMonths: null,
|
|
|
+ installmentStartDate: '',
|
|
|
+ installmentAdvanceDays: DEFAULT_INSTALLMENT_ADVANCE_DAYS,
|
|
|
+ installmentRatios: [],
|
|
|
+ schedules: amount
|
|
|
+ ? [
|
|
|
+ {
|
|
|
+ scheduleNo: 1,
|
|
|
+ dueAmount: amount,
|
|
|
+ dueDate: '',
|
|
|
+ scheduleStatus: 'open',
|
|
|
+ transfers: []
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ : []
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function rebuildSchedules(pay: Record<string, any>): Record<string, any> {
|
|
|
+ const mode = pay.payMode || 'dj'
|
|
|
+ if (mode === 'dj') {
|
|
|
+ return {
|
|
|
+ ...pay,
|
|
|
+ schedules: [
|
|
|
+ {
|
|
|
+ scheduleNo: 1,
|
|
|
+ dueAmount: pay.contractAmount,
|
|
|
+ dueDate: pay.lumpSumDeadline || '',
|
|
|
+ scheduleStatus: 'open',
|
|
|
+ transfers: (pay.schedules?.[0]?.transfers || []).filter(
|
|
|
+ (t: any) => t.payStatus === 'dfj' || !t.payStatus
|
|
|
+ )
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const count = Number(pay.installmentCount) || 0
|
|
|
+ const interval = Number(pay.installmentIntervalMonths) || 0
|
|
|
+ const startDate = pay.installmentStartDate || ''
|
|
|
+ const advanceDays = Number(pay.installmentAdvanceDays ?? DEFAULT_INSTALLMENT_ADVANCE_DAYS)
|
|
|
+ let ratios = parseInstallmentRatios(pay.installmentRatios)
|
|
|
+ if (count > 0) {
|
|
|
+ ratios = normalizeInstallmentAmounts(ratios, pay.contractAmount, count)
|
|
|
+ }
|
|
|
+ if (!count || !interval || !startDate) {
|
|
|
+ return { ...pay, installmentRatios: ratios, schedules: [] }
|
|
|
+ }
|
|
|
+ const details = generateInitialFqDetails(
|
|
|
+ {
|
|
|
+ installmentCount: count,
|
|
|
+ installmentIntervalMonths: interval,
|
|
|
+ installmentStartDate: startDate,
|
|
|
+ installmentAdvanceDays: advanceDays,
|
|
|
+ installmentRatios: ratios
|
|
|
+ },
|
|
|
+ pay.contractAmount
|
|
|
+ )
|
|
|
+ return {
|
|
|
+ ...pay,
|
|
|
+ installmentRatios: ratios,
|
|
|
+ schedules: details.map((d, i) => ({
|
|
|
+ scheduleNo: d.installmentNo || i + 1,
|
|
|
+ dueAmount: d.dueAmount,
|
|
|
+ dueDate: d.installmentDueDate || '',
|
|
|
+ scheduleStatus: 'open',
|
|
|
+ transfers: []
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const syncedPayments = computed(() => props.modelValue || [])
|
|
|
+
|
|
|
+ function emitChange() {
|
|
|
+ emit('update:modelValue', [...(props.modelValue || [])])
|
|
|
+ }
|
|
|
+
|
|
|
+ function patchPay(pIdx: number, patch: Record<string, any>, rebuild = false) {
|
|
|
+ const list = [...(props.modelValue || [])]
|
|
|
+ let next = { ...list[pIdx], ...patch }
|
|
|
+ if (rebuild) next = rebuildSchedules(next)
|
|
|
+ list[pIdx] = next
|
|
|
+ emit('update:modelValue', list)
|
|
|
+ }
|
|
|
+
|
|
|
+ function onPayModeChange(pIdx: number, mode: string) {
|
|
|
+ const preset = resolvePerSamplePreset(mode === 'fq' ? 'fq' : 'dj')
|
|
|
+ const patch: Record<string, any> = {
|
|
|
+ payMode: mode,
|
|
|
+ contractAmount: preset?.contractAmount ?? null
|
|
|
+ }
|
|
|
+ if (mode === 'fq' && preset) {
|
|
|
+ patch.installmentCount = preset.installmentCount || 6
|
|
|
+ patch.installmentIntervalMonths = 1
|
|
|
+ patch.installmentAdvanceDays = DEFAULT_INSTALLMENT_ADVANCE_DAYS
|
|
|
+ patch.installmentRatios = preset.installmentAmounts || []
|
|
|
+ if (!patch.installmentStartDate) {
|
|
|
+ /* keep existing start date if any */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ patchPay(pIdx, patch, true)
|
|
|
+ }
|
|
|
+
|
|
|
+ function onContractAmountChange(pIdx: number, v: number | null) {
|
|
|
+ patchPay(pIdx, { contractAmount: v }, true)
|
|
|
+ }
|
|
|
+
|
|
|
+ function onFqFieldChange(pIdx: number, key: string, raw: string) {
|
|
|
+ const num = raw === '' || raw == null ? null : Number(raw)
|
|
|
+ patchPay(pIdx, { [key]: num }, true)
|
|
|
+ }
|
|
|
+
|
|
|
+ function onFqStartDateChange(pIdx: number, v: string) {
|
|
|
+ patchPay(pIdx, { installmentStartDate: v || '' }, true)
|
|
|
+ }
|
|
|
+
|
|
|
+ function addTransfer(pIdx: number, sIdx: number) {
|
|
|
+ const list = [...(props.modelValue || [])]
|
|
|
+ const pay = { ...list[pIdx] }
|
|
|
+ const schedules = [...(pay.schedules || [])]
|
|
|
+ const sch = { ...schedules[sIdx] }
|
|
|
+ const transfers = [...(sch.transfers || [])]
|
|
|
+ transfers.push({
|
|
|
+ transferType: '',
|
|
|
+ transferAmount: null,
|
|
|
+ transferTime: '',
|
|
|
+ payStatus: 'dfj',
|
|
|
+ transferAttachments: ''
|
|
|
+ })
|
|
|
+ sch.transfers = transfers
|
|
|
+ schedules[sIdx] = sch
|
|
|
+ pay.schedules = schedules
|
|
|
+ list[pIdx] = pay
|
|
|
+ emit('update:modelValue', list)
|
|
|
+ }
|
|
|
+
|
|
|
+ function removeTransfer(pIdx: number, sIdx: number, tIdx: number) {
|
|
|
+ const list = [...(props.modelValue || [])]
|
|
|
+ const pay = { ...list[pIdx] }
|
|
|
+ const schedules = [...(pay.schedules || [])]
|
|
|
+ const sch = { ...schedules[sIdx] }
|
|
|
+ const transfers = [...(sch.transfers || [])]
|
|
|
+ transfers.splice(tIdx, 1)
|
|
|
+ sch.transfers = transfers
|
|
|
+ schedules[sIdx] = sch
|
|
|
+ pay.schedules = schedules
|
|
|
+ list[pIdx] = pay
|
|
|
+ emit('update:modelValue', list)
|
|
|
+ }
|
|
|
+
|
|
|
+ async function submitTransfer(tr: Record<string, any>) {
|
|
|
+ if (!tr.id) {
|
|
|
+ ElMessage.warning('请先保存协议后再提交财务确认')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ submittingId.value = String(tr.id)
|
|
|
+ try {
|
|
|
+ await fetchSubmitPaymentTransferFinance(String(tr.id))
|
|
|
+ tr.payStatus = 'jfqrz'
|
|
|
+ emitChange()
|
|
|
+ } finally {
|
|
|
+ submittingId.value = ''
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 样本增减时对齐缴费方案(按 sampleCode) */
|
|
|
+ watch(
|
|
|
+ () =>
|
|
|
+ (props.samples || [])
|
|
|
+ .filter((s) => String(s.sampleStatus || '') !== 'yzf' && String(s.sampleCode || '').trim())
|
|
|
+ .map((s) => String(s.sampleCode)),
|
|
|
+ (codes) => {
|
|
|
+ const codeSet = new Set(codes)
|
|
|
+ const existing = [...(props.modelValue || [])]
|
|
|
+ const byCode = new Map(existing.map((p) => [String(p.sampleCode || ''), p]))
|
|
|
+ const next: Array<Record<string, any>> = []
|
|
|
+ for (const sample of props.samples || []) {
|
|
|
+ const code = String(sample.sampleCode || '').trim()
|
|
|
+ if (!code || String(sample.sampleStatus || '') === 'yzf') continue
|
|
|
+ const prev = byCode.get(code)
|
|
|
+ if (prev) {
|
|
|
+ next.push({
|
|
|
+ ...prev,
|
|
|
+ protocolSampleId: sample.id || prev.protocolSampleId || ''
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ next.push(emptyDjPayment(sample))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 保留已有但样本暂无编码的方案(编辑中间态)
|
|
|
+ for (const p of existing) {
|
|
|
+ const code = String(p.sampleCode || '')
|
|
|
+ if (code && !codeSet.has(code) && p.id) {
|
|
|
+ /* 样本删除:不保留 */
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const same =
|
|
|
+ next.length === existing.length &&
|
|
|
+ next.every(
|
|
|
+ (p, i) =>
|
|
|
+ p.sampleCode === existing[i]?.sampleCode &&
|
|
|
+ p.protocolSampleId === existing[i]?.protocolSampleId
|
|
|
+ )
|
|
|
+ if (!same) emit('update:modelValue', next)
|
|
|
+ },
|
|
|
+ { immediate: true, deep: true }
|
|
|
+ )
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+ .sample-payment-panel {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sample-pay-card {
|
|
|
+ padding: 14px 16px;
|
|
|
+ border: 1px solid var(--el-border-color-lighter, #e2e8f0);
|
|
|
+ border-radius: 10px;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sample-pay-card__head {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sample-pay-card__title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--mint-text, #0f172a);
|
|
|
+ }
|
|
|
+
|
|
|
+ .ml-2 {
|
|
|
+ margin-left: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .section-subtitle {
|
|
|
+ margin: 12px 0 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: var(--mint-text-secondary, #475569);
|
|
|
+ }
|
|
|
+
|
|
|
+ .muted-hint {
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--mint-text-muted, #94a3b8);
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-block {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: var(--mint-surface-muted, #f8fafc);
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-block__head {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px 12px;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .schedule-arrears {
|
|
|
+ color: var(--mint-text-muted, #64748b);
|
|
|
+ }
|
|
|
+
|
|
|
+ .transfer-row {
|
|
|
+ margin-top: 6px;
|
|
|
+ padding-top: 6px;
|
|
|
+ border-top: 1px dashed #e2e8f0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-grid {
|
|
|
+ display: grid;
|
|
|
+ gap: 8px 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-grid--col4 {
|
|
|
+ grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+
|
|
|
+ @media (max-width: 1100px) {
|
|
|
+ .form-grid--col4 {
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|