1324 lines
47 KiB
Vue
1324 lines
47 KiB
Vue
<template>
|
||
<component
|
||
:is="editorContainer"
|
||
v-bind="editorContainerProps"
|
||
@update:model-value="visible = $event"
|
||
>
|
||
<div v-loading="loading" class="formal-editor">
|
||
<section-card title="结算基本信息">
|
||
<el-form
|
||
ref="formRef"
|
||
:model="form"
|
||
:rules="rules"
|
||
label-position="right"
|
||
label-width="auto"
|
||
>
|
||
<el-row :gutter="24">
|
||
<el-col v-for="field in fields" :key="field.prop" :span="6">
|
||
<el-form-item :label="field.label" :prop="field.prop">
|
||
<el-date-picker
|
||
v-if="field.type === 'date' && editable"
|
||
v-model="form[field.prop]"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="请选择"
|
||
/>
|
||
<el-select
|
||
v-else-if="field.type === 'project' && editable"
|
||
v-model="form.projectId"
|
||
filterable
|
||
clearable
|
||
placeholder="请选择项目"
|
||
:disabled="details.length > 0 || sources.length > 0"
|
||
@change="handleProjectChange"
|
||
>
|
||
<el-option
|
||
v-for="item in projects"
|
||
:key="item.id"
|
||
:label="item.name"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
<el-select
|
||
v-else-if="field.type === 'contract' && editable"
|
||
v-model="form.contractId"
|
||
filterable
|
||
remote
|
||
:remote-method="loadContracts"
|
||
:disabled="!form.projectId || details.length > 0 || sources.length > 0"
|
||
placeholder="请选择合同"
|
||
@change="handleContractChange"
|
||
>
|
||
<el-option
|
||
v-for="item in contracts"
|
||
:key="item.id"
|
||
:label="`${item.contractNo} / ${item.contractName}`"
|
||
:value="item.id"
|
||
/>
|
||
</el-select>
|
||
<el-input-number
|
||
v-else-if="field.type === 'number' && editable"
|
||
v-model="form[field.prop]"
|
||
:min="0.000001"
|
||
:precision="6"
|
||
:controls="false"
|
||
/>
|
||
<span v-else-if="field.money">{{ formatMoney(form[field.prop]) }}</span>
|
||
<span v-else-if="field.type === 'contract'">{{
|
||
displayValue(form.contractName)
|
||
}}</span>
|
||
<span v-else-if="field.type === 'project'">{{
|
||
displayValue(form.projectName)
|
||
}}</span>
|
||
<span v-else>{{ displayValue(form[field.prop]) }}</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="24">
|
||
<el-form-item label="备注" prop="remark">
|
||
<el-input
|
||
v-if="editable"
|
||
v-model="form.remark"
|
||
type="textarea"
|
||
maxlength="200"
|
||
show-word-limit
|
||
/>
|
||
<span v-else>{{ displayValue(form.remark) }}</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</section-card>
|
||
|
||
<section-card title="结算合计">
|
||
<template #extra>
|
||
<el-button v-if="editable" type="primary" plain @click="addSummaryFee">
|
||
添加费用
|
||
</el-button>
|
||
</template>
|
||
<el-table :data="summaryFees" border show-summary :summary-method="getSummarySums">
|
||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||
<el-table-column
|
||
v-for="column in summaryTableColumns"
|
||
:key="column.prop"
|
||
:label="column.label"
|
||
:prop="column.prop"
|
||
:min-width="column.minWidth"
|
||
align="center"
|
||
>
|
||
<template #default="{ row }">
|
||
<el-select
|
||
v-if="editable && column.prop === 'feeType' && row.manualFlag === 1"
|
||
v-model="row.feeType"
|
||
filterable
|
||
placeholder="请选择"
|
||
@change="handleManualFeeTypeChange(row)"
|
||
>
|
||
<el-option
|
||
v-for="item in feeOptions"
|
||
:key="item.feeType"
|
||
:label="item.feeTypeName || item.feeType"
|
||
:value="item.feeType"
|
||
/>
|
||
</el-select>
|
||
<el-select
|
||
v-else-if="editable && column.prop === 'feeItem' && row.manualFlag === 1"
|
||
v-model="row.feeItem"
|
||
filterable
|
||
placeholder="请选择"
|
||
>
|
||
<el-option
|
||
v-for="name in manualFeeItems(row.feeType)"
|
||
:key="name"
|
||
:label="name"
|
||
:value="name"
|
||
/>
|
||
</el-select>
|
||
<el-input-number
|
||
v-else-if="editable && column.prop === 'adjustAmount'"
|
||
v-model="row.adjustAmount"
|
||
:precision="2"
|
||
:step="0.01"
|
||
:controls="false"
|
||
@change="recalculateSummaryRow(row)"
|
||
/>
|
||
<el-input
|
||
v-else-if="editable && column.prop === 'remark'"
|
||
v-model="row.remark"
|
||
maxlength="50"
|
||
show-word-limit
|
||
/>
|
||
<span v-else-if="isSummaryMoney(column.prop)">
|
||
{{ formatMoney(row[column.prop]) }}
|
||
</span>
|
||
<span v-else-if="column.prop === 'feeType'">{{ feeCategoryName(row.feeType) }}</span>
|
||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column v-if="editable" label="操作" width="100" align="center">
|
||
<template #default="{ row, $index }">
|
||
<el-link v-if="row.manualFlag === 1" type="danger" @click="removeSummaryFee($index)">
|
||
删除
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</section-card>
|
||
|
||
<section-card title="结算明细">
|
||
<template #extra>
|
||
<div class="formal-editor__actions">
|
||
<el-button type="primary" text @click="detailCollapsed = !detailCollapsed">
|
||
{{ detailCollapsed ? '展开明细' : '收起明细' }}
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
<el-form
|
||
:model="detailQuery"
|
||
inline
|
||
label-position="right"
|
||
label-width="160px"
|
||
class="formal-editor__detail-filter"
|
||
@submit.prevent
|
||
>
|
||
<el-form-item label="单据号">
|
||
<el-input v-model="detailQuery.documentNo" clearable @keyup.enter="handleDetailQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="运单号">
|
||
<el-input v-model="detailQuery.waybillNo" clearable @keyup.enter="handleDetailQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="批次号">
|
||
<el-input v-model="detailQuery.batchNo" clearable @keyup.enter="handleDetailQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="货物名称">
|
||
<el-input v-model="detailQuery.cargoName" clearable @keyup.enter="handleDetailQuery" />
|
||
</el-form-item>
|
||
<el-form-item class="formal-editor__detail-filter-actions">
|
||
<el-button type="primary" @click="handleDetailQuery">查询</el-button>
|
||
<el-button @click="resetDetailQuery">重置</el-button>
|
||
<el-button type="primary" :disabled="!details.length" @click="exportDetails">
|
||
导出
|
||
</el-button>
|
||
<el-button v-if="editable" type="primary" @click="openDetailDialog">
|
||
选择结算明细
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
<el-table v-show="!detailCollapsed" :data="filteredDetails" border>
|
||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||
<el-table-column
|
||
v-for="column in detailTableColumns"
|
||
:key="column.prop"
|
||
v-bind="column"
|
||
align="center"
|
||
show-overflow-tooltip
|
||
>
|
||
<template #default="{ row }">
|
||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column v-if="editable" label="操作" width="150" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<div class="formal-editor__links">
|
||
<el-link v-if="row.formalSettlementId" type="primary" @click="openAdjustDialog(row)"
|
||
>调整</el-link
|
||
>
|
||
<el-link v-if="!row.sourcePreSettlementId" type="danger" @click="removeDetail(row)"
|
||
>删除</el-link
|
||
>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</section-card>
|
||
|
||
<section-card title="附件">
|
||
<vehicle-attachment-upload
|
||
v-model="attachments"
|
||
:readonly="!editable"
|
||
:multiple="true"
|
||
:limit="20"
|
||
:max-size="500"
|
||
:file-types="attachmentFileTypes"
|
||
/>
|
||
</section-card>
|
||
|
||
<section-card v-if="readonly && payments.length" title="付款信息">
|
||
<el-table :data="payments" border>
|
||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||
<el-table-column
|
||
v-for="column in paymentTableColumns"
|
||
:key="column.prop"
|
||
v-bind="column"
|
||
align="center"
|
||
>
|
||
<template #default="{ row }">
|
||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||
<span v-else-if="column.prop === 'paymentTypeName'">{{
|
||
row.paymentType === 'final' ? '尾款' : displayValue(row.paymentType)
|
||
}}</span>
|
||
<span v-else-if="column.prop === 'billStatusName'">{{
|
||
row.billStatus === 'reviewing' ? '审批中' : displayValue(row.billStatus)
|
||
}}</span>
|
||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</section-card>
|
||
</div>
|
||
<template v-if="!pageMode" #footer>
|
||
<el-button @click="visible = false">取消</el-button>
|
||
<el-button v-if="editable" type="primary" :loading="saving" @click="handleSave"
|
||
>提交</el-button
|
||
>
|
||
</template>
|
||
<div v-if="pageMode" class="formal-editor__page-actions">
|
||
<el-button @click="visible = false">取消</el-button>
|
||
<el-button v-if="editable" type="primary" :loading="saving" @click="handleSave">
|
||
提交
|
||
</el-button>
|
||
</div>
|
||
|
||
<el-dialog v-model="candidate.visible" title="选择预结算单" width="88%" append-to-body>
|
||
<el-form
|
||
:model="candidate.query"
|
||
inline
|
||
label-position="right"
|
||
label-width="160px"
|
||
class="formal-editor__candidate-search"
|
||
>
|
||
<el-form-item label="预结算单号"
|
||
><el-input v-model="candidate.query.preSettlementNo" clearable
|
||
/></el-form-item>
|
||
<el-form-item label="合同编号"
|
||
><el-input v-model="candidate.query.contractNo" clearable
|
||
/></el-form-item>
|
||
<el-form-item
|
||
><el-button @click="resetCandidates">重置</el-button
|
||
><el-button type="primary" @click="loadCandidates">查询</el-button></el-form-item
|
||
>
|
||
</el-form>
|
||
<el-table
|
||
ref="candidateTable"
|
||
v-loading="candidate.loading"
|
||
:data="candidate.rows"
|
||
border
|
||
@selection-change="candidate.selected = $event"
|
||
>
|
||
<el-table-column type="selection" width="52" fixed="left" align="center" />
|
||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||
<el-table-column
|
||
v-for="column in candidateTableColumns"
|
||
:key="column.prop"
|
||
v-bind="column"
|
||
align="center"
|
||
>
|
||
<template #default="{ row }"
|
||
><span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span
|
||
><span v-else>{{ displayValue(row[column.prop]) }}</span></template
|
||
>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="formal-editor__pagination">
|
||
<el-pagination
|
||
v-model:current-page="candidate.page.current"
|
||
v-model:page-size="candidate.page.size"
|
||
:total="candidate.page.total"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@current-change="loadCandidates"
|
||
@size-change="loadCandidates"
|
||
/>
|
||
</div>
|
||
<template #footer
|
||
><el-button @click="candidate.visible = false">取消</el-button
|
||
><el-button type="primary" @click="confirmCandidates">确定</el-button></template
|
||
>
|
||
</el-dialog>
|
||
|
||
<el-dialog v-model="detailCandidate.visible" title="选择结算明细" width="92%" append-to-body>
|
||
<el-form
|
||
:model="detailCandidate.query"
|
||
inline
|
||
label-position="right"
|
||
label-width="160px"
|
||
class="formal-editor__detail-search"
|
||
>
|
||
<el-form-item label="批次号"
|
||
><el-input v-model="detailCandidate.query.batchNo" clearable
|
||
/></el-form-item>
|
||
<el-form-item label="创建时间"
|
||
><el-date-picker
|
||
v-model="detailCandidate.query.createTimeRange"
|
||
type="daterange"
|
||
value-format="YYYY-MM-DD"
|
||
format="YYYY-MM-DD"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
/></el-form-item>
|
||
<el-form-item
|
||
><el-button @click="resetDetailCandidates">重置</el-button
|
||
><el-button type="primary" @click="loadDetailCandidates">查询</el-button></el-form-item
|
||
>
|
||
</el-form>
|
||
<el-table
|
||
v-loading="detailCandidate.loading"
|
||
:data="detailCandidate.rows"
|
||
border
|
||
@selection-change="detailCandidate.selected = $event"
|
||
>
|
||
<el-table-column type="selection" width="52" fixed="left" align="center" />
|
||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||
<el-table-column
|
||
v-for="column in candidateDetailTableColumns"
|
||
:key="column.prop"
|
||
v-bind="column"
|
||
align="center"
|
||
>
|
||
<template #default="{ row }"
|
||
><span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span
|
||
><span v-else>{{ displayValue(row[column.prop]) }}</span></template
|
||
>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="formal-editor__pagination">
|
||
<el-pagination
|
||
v-model:current-page="detailCandidate.page.current"
|
||
v-model:page-size="detailCandidate.page.size"
|
||
:total="detailCandidate.page.total"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@current-change="loadDetailCandidates"
|
||
@size-change="loadDetailCandidates"
|
||
/>
|
||
</div>
|
||
<template #footer
|
||
><el-button @click="detailCandidate.visible = false">取消</el-button
|
||
><el-button type="primary" @click="confirmDetailCandidates">确定</el-button></template
|
||
>
|
||
</el-dialog>
|
||
|
||
<el-dialog v-model="adjust.visible" title="结算明细调整" width="92%" append-to-body>
|
||
<el-table v-loading="adjust.loading" :data="adjust.rows" border>
|
||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||
<el-table-column prop="cargoName" label="货物名称" min-width="130" align="center" />
|
||
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
|
||
<el-table-column label="运输总量" min-width="130" align="center"
|
||
><template #default="{ row }"
|
||
><el-input-number
|
||
v-model="row.transportQuantity"
|
||
:min="0"
|
||
:precision="6"
|
||
:controls="false" /></template
|
||
></el-table-column>
|
||
<el-table-column label="里程(KM)" min-width="130" align="center"
|
||
><template #default="{ row }"
|
||
><el-input-number
|
||
v-model="row.mileage"
|
||
:min="0"
|
||
:precision="2"
|
||
:controls="false" /></template
|
||
></el-table-column>
|
||
<el-table-column label="运输单价" min-width="130" align="center"
|
||
><template #default="{ row }"
|
||
><el-input-number
|
||
v-model="row.unitPrice"
|
||
:min="0"
|
||
:precision="2"
|
||
:controls="false" /></template
|
||
></el-table-column>
|
||
<el-table-column label="运费" min-width="130" align="center"
|
||
><template #default="{ row }"
|
||
><el-input-number
|
||
v-model="row.freightAmount"
|
||
:min="0"
|
||
:precision="2"
|
||
:controls="false" /></template
|
||
></el-table-column>
|
||
<el-table-column label="结算金额(含税)" min-width="165" align="center"
|
||
><template #default="{ row }"
|
||
><el-input-number
|
||
v-model="row.settlementAmountTax"
|
||
:min="0"
|
||
:precision="2"
|
||
:controls="false" /></template
|
||
></el-table-column>
|
||
<el-table-column label="结算金额(不含税)" min-width="180" align="center"
|
||
><template #default="{ row }"
|
||
><el-input-number
|
||
v-model="row.settlementAmountNoTax"
|
||
:min="0"
|
||
:precision="2"
|
||
:controls="false" /></template
|
||
></el-table-column>
|
||
<el-table-column label="备注" min-width="180" align="center"
|
||
><template #default="{ row }"><el-input v-model="row.remark" maxlength="200" /></template
|
||
></el-table-column>
|
||
</el-table>
|
||
<el-form label-position="right" label-width="auto" class="formal-editor__adjust-reason">
|
||
<el-form-item label="调整原因" required
|
||
><el-input v-model="adjust.reason" type="textarea" maxlength="200" show-word-limit
|
||
/></el-form-item>
|
||
</el-form>
|
||
<template #footer
|
||
><el-button @click="adjust.visible = false">取消</el-button
|
||
><el-button type="primary" :loading="adjust.saving" @click="saveAdjustment"
|
||
>保存</el-button
|
||
></template
|
||
>
|
||
</el-dialog>
|
||
</component>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
adjustDetail,
|
||
getCandidateDetails,
|
||
getCandidates,
|
||
getContractOptions,
|
||
getDetail,
|
||
getDetailFees,
|
||
getFeeOptions,
|
||
getNextNo,
|
||
save,
|
||
} from '@/api/settlement/formalSettlement';
|
||
import { getDetail as getPreSettlementDetail } from '@/api/settlement/preSettlement';
|
||
import {
|
||
createFormalSettlementForm,
|
||
formalSettlementFormFields,
|
||
} from '@/option/settlement/formalSettlementForm';
|
||
import {
|
||
candidateColumns,
|
||
candidateDetailColumns,
|
||
detailColumns,
|
||
paymentColumns,
|
||
sourceColumns,
|
||
summaryColumns,
|
||
} from '@/option/settlement/formalSettlementTable';
|
||
import * as XLSX from 'xlsx';
|
||
|
||
export default {
|
||
name: 'FormalSettlementEditor',
|
||
props: {
|
||
modelValue: Boolean,
|
||
recordId: [String, Number],
|
||
readonly: Boolean,
|
||
pageMode: Boolean,
|
||
initialData: {
|
||
type: Object,
|
||
default: null,
|
||
},
|
||
},
|
||
emits: ['update:modelValue', 'success'],
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
saving: false,
|
||
form: createFormalSettlementForm(),
|
||
sources: [],
|
||
details: [],
|
||
summaryFees: [],
|
||
payments: [],
|
||
attachments: [],
|
||
attachmentFileTypes: [
|
||
'pdf',
|
||
'bmp',
|
||
'jpeg',
|
||
'png',
|
||
'jpg',
|
||
'doc',
|
||
'docx',
|
||
'ppt',
|
||
'pptx',
|
||
'xlsx',
|
||
'xls',
|
||
'eml',
|
||
'msg',
|
||
'zip',
|
||
'rar',
|
||
],
|
||
contracts: [],
|
||
allContracts: [],
|
||
projects: [],
|
||
feeOptions: [],
|
||
fields: formalSettlementFormFields,
|
||
sourceTableColumns: sourceColumns,
|
||
summaryTableColumns: summaryColumns,
|
||
detailTableColumns: detailColumns,
|
||
paymentTableColumns: paymentColumns,
|
||
candidateTableColumns: candidateColumns,
|
||
candidateDetailTableColumns: candidateDetailColumns,
|
||
rules: {
|
||
projectId: [{ required: true, message: '请选择项目', trigger: 'change' }],
|
||
contractId: [{ required: true, message: '请选择合同', trigger: 'change' }],
|
||
exchangeRateDate: [{ required: true, message: '请选择汇率日期', trigger: 'change' }],
|
||
exchangeRate: [{ required: true, message: '请输入结算汇率', trigger: 'blur' }],
|
||
},
|
||
candidate: {
|
||
visible: false,
|
||
loading: false,
|
||
query: {},
|
||
rows: [],
|
||
selected: [],
|
||
page: { current: 1, size: 10, total: 0 },
|
||
},
|
||
detailCandidate: {
|
||
visible: false,
|
||
loading: false,
|
||
query: { batchNo: '', createTimeRange: [] },
|
||
rows: [],
|
||
selected: [],
|
||
page: { current: 1, size: 10, total: 0 },
|
||
},
|
||
adjust: {
|
||
visible: false,
|
||
loading: false,
|
||
saving: false,
|
||
detailId: null,
|
||
reason: '',
|
||
rows: [],
|
||
},
|
||
detailCollapsed: false,
|
||
detailQuery: {
|
||
documentNo: '',
|
||
waybillNo: '',
|
||
batchNo: '',
|
||
cargoName: '',
|
||
},
|
||
appliedDetailQuery: {
|
||
documentNo: '',
|
||
waybillNo: '',
|
||
batchNo: '',
|
||
cargoName: '',
|
||
},
|
||
};
|
||
},
|
||
computed: {
|
||
visible: {
|
||
get() {
|
||
return this.modelValue;
|
||
},
|
||
set(value) {
|
||
this.$emit('update:modelValue', value);
|
||
},
|
||
},
|
||
editorContainer() {
|
||
return this.pageMode ? 'div' : 'el-dialog';
|
||
},
|
||
editorContainerProps() {
|
||
if (this.pageMode) {
|
||
return { class: 'formal-editor-shell formal-editor-shell--page' };
|
||
}
|
||
return {
|
||
modelValue: this.visible,
|
||
title: this.title,
|
||
width: '96%',
|
||
top: '2vh',
|
||
appendToBody: true,
|
||
destroyOnClose: true,
|
||
};
|
||
},
|
||
editable() {
|
||
return !this.readonly;
|
||
},
|
||
title() {
|
||
return this.readonly ? '查看正式结算单' : this.recordId ? '编辑正式结算单' : '新增正式结算单';
|
||
},
|
||
summaryTotal() {
|
||
return this.summaryFees.reduce((total, row) => total + Number(row.settlementAmount || 0), 0);
|
||
},
|
||
filteredDetails() {
|
||
return this.details.filter(row =>
|
||
Object.entries(this.appliedDetailQuery).every(([field, keyword]) => {
|
||
if (!keyword?.trim()) return true;
|
||
return String(row[field] || '')
|
||
.toLocaleLowerCase()
|
||
.includes(keyword.trim().toLocaleLowerCase());
|
||
})
|
||
);
|
||
},
|
||
},
|
||
watch: {
|
||
modelValue: {
|
||
immediate: true,
|
||
handler(value) {
|
||
if (value) this.initialize();
|
||
},
|
||
},
|
||
summaryTotal(value) {
|
||
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
||
this.form.localSettlementAmount = (
|
||
Number(value || 0) * Number(this.form.exchangeRate || 1)
|
||
).toFixed(2);
|
||
},
|
||
'form.exchangeRate'(value) {
|
||
this.form.localSettlementAmount = (
|
||
Number(this.summaryTotal || 0) * Number(value || 1)
|
||
).toFixed(2);
|
||
},
|
||
},
|
||
methods: {
|
||
async initialize() {
|
||
this.form = createFormalSettlementForm();
|
||
this.sources = [];
|
||
this.details = [];
|
||
this.summaryFees = [];
|
||
this.payments = [];
|
||
this.attachments = [];
|
||
this.detailCollapsed = false;
|
||
this.resetDetailQuery(false);
|
||
await Promise.all([this.loadAllContracts(), this.loadFeeOptions()]);
|
||
if (!this.recordId) {
|
||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||
const response = await getNextNo();
|
||
this.form.formalSettlementNo = this.unwrapData(response) || '';
|
||
if (this.initialData) await this.applyInitialData();
|
||
return;
|
||
}
|
||
this.loading = true;
|
||
try {
|
||
const response = await getDetail(this.recordId);
|
||
const data = this.unwrapData(response);
|
||
this.form = {
|
||
...createFormalSettlementForm(),
|
||
...data,
|
||
sourcePreSettlementIds: (data.sources || []).map(item => item.preSettlementId),
|
||
sourceDetailIds: (data.details || [])
|
||
.filter(item => !item.sourcePreSettlementId)
|
||
.map(item => item.sourceDetailId),
|
||
};
|
||
this.sources = data.sources || [];
|
||
this.details = data.details || [];
|
||
this.summaryFees = data.summaryFees || [];
|
||
this.payments = data.payments || [];
|
||
this.attachments = this.parseAttachments(data.attachmentsJson);
|
||
this.contracts = this.allContracts.filter(
|
||
item => String(item.projectId) === String(this.form.projectId)
|
||
);
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
async applyInitialData() {
|
||
const sourcePreSettlements = Array.isArray(this.initialData?.sourcePreSettlements)
|
||
? this.initialData.sourcePreSettlements
|
||
: [];
|
||
if (sourcePreSettlements.length) {
|
||
await this.applyInitialSources(sourcePreSettlements);
|
||
return;
|
||
}
|
||
const rows = Array.isArray(this.initialData?.rows) ? this.initialData.rows : [];
|
||
if (!rows.length) return;
|
||
const first = rows[0];
|
||
const settlementType = this.initialData.settlementType || first.settlementType || 'payable';
|
||
const matchedContract = this.allContracts.find(
|
||
item =>
|
||
(first.contractId && String(item.id) === String(first.contractId)) ||
|
||
(first.contractNo && String(item.contractNo) === String(first.contractNo))
|
||
);
|
||
const contractId = first.contractId || matchedContract?.id || null;
|
||
const projectId = first.projectId || matchedContract?.projectId || null;
|
||
const projectName = first.projectName || matchedContract?.projectName || '';
|
||
this.contracts = this.allContracts.filter(
|
||
item => projectId && String(item.projectId) === String(projectId)
|
||
);
|
||
if (projectId && !this.projects.some(item => String(item.id) === String(projectId))) {
|
||
this.projects.push({ id: projectId, name: projectName });
|
||
}
|
||
if (!this.contracts.some(item => String(item.id) === String(contractId))) {
|
||
this.contracts.push({
|
||
id: contractId,
|
||
contractNo: first.contractNo,
|
||
contractName: first.contractName,
|
||
projectId,
|
||
projectName,
|
||
deptId: first.deptId || matchedContract?.deptId,
|
||
deptName: first.deptName || matchedContract?.deptName,
|
||
payerName: first.payerName,
|
||
payeeName: first.payeeName,
|
||
partyA: matchedContract?.partyA,
|
||
partyB: matchedContract?.partyB,
|
||
settlementType,
|
||
});
|
||
}
|
||
this.handleContractChange(contractId);
|
||
Object.assign(this.form, {
|
||
contractId,
|
||
contractNo: first.contractNo || this.form.contractNo,
|
||
contractName: first.contractName || this.form.contractName,
|
||
projectId: projectId || this.form.projectId,
|
||
projectName: projectName || this.form.projectName,
|
||
deptId: first.deptId || this.form.deptId,
|
||
deptName: first.deptName || this.form.deptName,
|
||
payerName: first.payerName || this.form.payerName,
|
||
payeeName: first.payeeName || this.form.payeeName,
|
||
settlementType,
|
||
settlementTypeName: settlementType === 'receivable' ? '应收' : '应付',
|
||
currency: first.currency || 'RMB',
|
||
});
|
||
this.details = rows.map(row => ({
|
||
...row,
|
||
sourceDetailId: row.sourceDetailId || row.id,
|
||
settlementAmountTax:
|
||
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
|
||
}));
|
||
this.form.sourceDetailIds = this.details.map(item => item.sourceDetailId);
|
||
this.buildSummaryFeesFromDetails();
|
||
},
|
||
async applyInitialSources(rows) {
|
||
const first = rows[0];
|
||
const contractId = first.contractId || null;
|
||
const settlementType = first.settlementType || 'payable';
|
||
if (!this.contracts.some(item => String(item.id) === String(contractId))) {
|
||
this.contracts.push({
|
||
id: contractId,
|
||
contractNo: first.contractNo,
|
||
contractName: first.contractName,
|
||
projectId: first.projectId,
|
||
projectName: first.projectName,
|
||
deptId: first.deptId,
|
||
deptName: first.deptName,
|
||
payerName: first.payerName,
|
||
payeeName: first.payeeName,
|
||
settlementType,
|
||
});
|
||
}
|
||
this.handleContractChange(contractId);
|
||
this.sources = rows.map(row => ({
|
||
...row,
|
||
preSettlementId: row.preSettlementId || row.id,
|
||
}));
|
||
this.form.sourcePreSettlementIds = this.sources.map(item => item.preSettlementId);
|
||
Object.assign(this.form, first, {
|
||
id: null,
|
||
formalSettlementNo: this.form.formalSettlementNo,
|
||
contractId,
|
||
settlementType,
|
||
settlementTypeName: settlementType === 'receivable' ? '应收' : '应付',
|
||
sourcePreSettlementIds: this.form.sourcePreSettlementIds,
|
||
});
|
||
const responses = await Promise.all(
|
||
this.sources.map(item => getPreSettlementDetail(item.preSettlementId))
|
||
);
|
||
const settlements = responses.map(response => this.unwrapData(response) || {});
|
||
this.details = settlements.flatMap(settlement =>
|
||
(settlement.details || []).map(detail => ({
|
||
...detail,
|
||
sourcePreSettlementId: settlement.id,
|
||
}))
|
||
);
|
||
const summaryMap = new Map();
|
||
settlements
|
||
.flatMap(settlement => settlement.summaryFees || [])
|
||
.forEach(row => {
|
||
const key = `${row.feeType}\u0000${row.feeItem}`;
|
||
const current = summaryMap.get(key) || {
|
||
feeType: row.feeType,
|
||
feeItem: row.feeItem,
|
||
originalAmount: 0,
|
||
adjustAmount: 0,
|
||
settlementAmount: 0,
|
||
remark: '',
|
||
manualFlag: 0,
|
||
};
|
||
current.originalAmount += Number(row.settlementAmount || 0);
|
||
current.settlementAmount = current.originalAmount;
|
||
summaryMap.set(key, current);
|
||
});
|
||
this.summaryFees = [...summaryMap.values()];
|
||
if (!this.summaryFees.length) this.buildSummaryFeesFromDetails();
|
||
},
|
||
async loadAllContracts() {
|
||
const response = await getContractOptions('');
|
||
this.allContracts = this.unwrapData(response) || [];
|
||
const projectMap = new Map();
|
||
this.allContracts.forEach(item => {
|
||
if (item.projectId)
|
||
projectMap.set(String(item.projectId), { id: item.projectId, name: item.projectName });
|
||
});
|
||
this.projects = [...projectMap.values()];
|
||
this.contracts = [];
|
||
},
|
||
async loadContracts(keyword = '') {
|
||
const projectId = this.form.projectId;
|
||
if (!projectId) {
|
||
this.contracts = [];
|
||
return;
|
||
}
|
||
const response = await getContractOptions(keyword, projectId);
|
||
if (String(this.form.projectId) === String(projectId)) {
|
||
this.contracts = this.unwrapData(response) || [];
|
||
}
|
||
},
|
||
async loadFeeOptions() {
|
||
const response = await getFeeOptions();
|
||
this.feeOptions = this.unwrapData(response) || [];
|
||
},
|
||
handleProjectChange(id) {
|
||
const project = this.projects.find(item => String(item.id) === String(id));
|
||
this.form.projectName = project?.name || '';
|
||
this.form.contractId = null;
|
||
this.form.contractNo = '';
|
||
this.form.contractName = '';
|
||
this.form.deptId = null;
|
||
this.form.deptName = '';
|
||
this.form.payerName = '';
|
||
this.form.payeeName = '';
|
||
this.sources = [];
|
||
this.details = [];
|
||
this.summaryFees = [];
|
||
this.form.sourcePreSettlementIds = [];
|
||
this.form.sourceDetailIds = [];
|
||
this.loadContracts();
|
||
},
|
||
handleContractChange(id) {
|
||
const contract = this.contracts.find(item => String(item.id) === String(id));
|
||
if (!contract) return;
|
||
const formalSettlementId = this.form.id;
|
||
Object.assign(this.form, contract, {
|
||
id: formalSettlementId,
|
||
contractId: contract.id,
|
||
payerName:
|
||
contract.payerName ||
|
||
(contract.settlementType === 'receivable' ? contract.partyB : contract.partyA),
|
||
payeeName:
|
||
contract.payeeName ||
|
||
(contract.settlementType === 'receivable' ? contract.partyA : contract.partyB),
|
||
settlementTypeName: contract.settlementType === 'receivable' ? '应收' : '应付',
|
||
});
|
||
this.sources = [];
|
||
this.details = [];
|
||
this.summaryFees = [];
|
||
this.form.sourcePreSettlementIds = [];
|
||
this.form.sourceDetailIds = [];
|
||
},
|
||
openCandidateDialog() {
|
||
this.candidate.visible = true;
|
||
this.candidate.page.current = 1;
|
||
this.loadCandidates();
|
||
},
|
||
async loadCandidates() {
|
||
this.candidate.loading = true;
|
||
try {
|
||
const response = await getCandidates(
|
||
this.candidate.page.current,
|
||
this.candidate.page.size,
|
||
{
|
||
...this.candidate.query,
|
||
contractId: this.form.contractId || undefined,
|
||
}
|
||
);
|
||
const data = this.unwrapData(response);
|
||
this.candidate.rows = data.records || [];
|
||
this.candidate.page.total = data.total || 0;
|
||
} finally {
|
||
this.candidate.loading = false;
|
||
}
|
||
},
|
||
resetCandidates() {
|
||
this.candidate.query = {};
|
||
this.candidate.page.current = 1;
|
||
this.loadCandidates();
|
||
},
|
||
confirmCandidates() {
|
||
const merged = [...this.sources, ...this.candidate.selected];
|
||
const map = new Map(
|
||
merged.map(item => [
|
||
String(item.preSettlementId || item.id),
|
||
{ ...item, preSettlementId: item.preSettlementId || item.id },
|
||
])
|
||
);
|
||
this.sources = [...map.values()];
|
||
this.form.sourcePreSettlementIds = this.sources.map(item => item.preSettlementId);
|
||
if (this.sources.length) {
|
||
Object.assign(this.form, this.sources[0], {
|
||
contractId: this.sources[0].contractId,
|
||
formalSettlementNo: this.form.formalSettlementNo,
|
||
});
|
||
}
|
||
this.form.settlementAmount = this.sources.reduce(
|
||
(sum, item) => sum + Number(item.settlementAmount || 0),
|
||
0
|
||
);
|
||
this.form.localSettlementAmount =
|
||
this.form.settlementAmount * Number(this.form.exchangeRate || 1);
|
||
this.candidate.visible = false;
|
||
},
|
||
removeSource(index) {
|
||
this.sources.splice(index, 1);
|
||
this.form.sourcePreSettlementIds = this.sources.map(item => item.preSettlementId || item.id);
|
||
},
|
||
openDetailDialog() {
|
||
if (!this.form.contractId) return this.$message.warning('请先选择合同');
|
||
this.detailCandidate.visible = true;
|
||
this.detailCandidate.page.current = 1;
|
||
this.detailCandidate.selected = [];
|
||
this.loadDetailCandidates();
|
||
},
|
||
async loadDetailCandidates() {
|
||
this.detailCandidate.loading = true;
|
||
try {
|
||
const range = this.detailCandidate.query.createTimeRange || [];
|
||
const params = {
|
||
contractId: this.form.contractId,
|
||
settlementType: this.form.settlementType,
|
||
batchNo: this.detailCandidate.query.batchNo,
|
||
createStartDate: range[0],
|
||
createEndDate: range[1],
|
||
};
|
||
const response = await getCandidateDetails(
|
||
this.detailCandidate.page.current,
|
||
this.detailCandidate.page.size,
|
||
params
|
||
);
|
||
const data = this.unwrapData(response);
|
||
this.detailCandidate.rows = data.records || [];
|
||
this.detailCandidate.page.total = data.total || 0;
|
||
} finally {
|
||
this.detailCandidate.loading = false;
|
||
}
|
||
},
|
||
resetDetailCandidates() {
|
||
this.detailCandidate.query = { batchNo: '', createTimeRange: [] };
|
||
this.detailCandidate.page.current = 1;
|
||
this.loadDetailCandidates();
|
||
},
|
||
confirmDetailCandidates() {
|
||
if (!this.detailCandidate.selected.length) {
|
||
return this.$message.warning('请选择结算明细');
|
||
}
|
||
const existing = new Map(
|
||
this.details
|
||
.filter(item => !item.formalSettlementId)
|
||
.map(item => [String(item.sourceDetailId || item.id), item])
|
||
);
|
||
this.detailCandidate.selected.forEach(item =>
|
||
existing.set(String(item.id), {
|
||
...item,
|
||
sourceDetailId: item.id,
|
||
settlementAmountTax: item.totalAmount,
|
||
})
|
||
);
|
||
this.details = [
|
||
...this.details.filter(item => item.formalSettlementId),
|
||
...existing.values(),
|
||
];
|
||
this.form.sourceDetailIds = [...existing.values()].map(item => item.sourceDetailId);
|
||
this.buildSummaryFeesFromDetails();
|
||
this.detailCandidate.visible = false;
|
||
},
|
||
removeDetail(row) {
|
||
const index = this.details.indexOf(row);
|
||
if (index < 0) return;
|
||
this.details.splice(index, 1);
|
||
this.form.sourceDetailIds = this.details
|
||
.filter(item => !item.sourcePreSettlementId)
|
||
.map(item => item.sourceDetailId);
|
||
this.buildSummaryFeesFromDetails();
|
||
},
|
||
addSummaryFee() {
|
||
this.summaryFees.push({
|
||
feeType: '',
|
||
feeItem: '',
|
||
originalAmount: 0,
|
||
adjustAmount: 0,
|
||
settlementAmount: 0,
|
||
remark: '',
|
||
manualFlag: 1,
|
||
});
|
||
},
|
||
removeSummaryFee(index) {
|
||
this.summaryFees.splice(index, 1);
|
||
},
|
||
handleManualFeeTypeChange(row) {
|
||
if (!this.manualFeeItems(row.feeType).includes(row.feeItem)) row.feeItem = '';
|
||
},
|
||
manualFeeItems(feeType) {
|
||
return this.feeOptions.find(item => item.feeType === feeType)?.feeItems || [];
|
||
},
|
||
feeCategoryName(value) {
|
||
return (
|
||
this.feeOptions.find(item => String(item.feeType) === String(value))?.feeTypeName || value
|
||
);
|
||
},
|
||
isSummaryMoney(prop) {
|
||
return ['originalAmount', 'adjustAmount', 'settlementAmount'].includes(prop);
|
||
},
|
||
recalculateSummaryRow(row) {
|
||
row.settlementAmount = Number(row.originalAmount || 0) + Number(row.adjustAmount || 0);
|
||
},
|
||
getSummarySums({ columns, data }) {
|
||
const sumProps = ['originalAmount', 'adjustAmount', 'settlementAmount'];
|
||
return columns.map((column, index) => {
|
||
if (index === 0) return '合计';
|
||
if (!sumProps.includes(column.property)) return '';
|
||
const total = data.reduce((sum, row) => sum + Number(row[column.property] || 0), 0);
|
||
return this.formatMoney(total);
|
||
});
|
||
},
|
||
buildSummaryFeesFromDetails() {
|
||
const generatedMap = new Map();
|
||
const existingGenerated = new Map(
|
||
this.summaryFees
|
||
.filter(row => row.manualFlag !== 1)
|
||
.map(row => [`${row.feeType}\u0000${row.feeItem}`, row])
|
||
);
|
||
const append = (feeItem, value, fallbackFeeType = '') => {
|
||
const amount = Number(value || 0);
|
||
if (!feeItem || !Number.isFinite(amount) || Math.abs(amount) < 0.005) return;
|
||
const option = this.feeOptions.find(item =>
|
||
(item.feeItems || []).some(name => String(name) === String(feeItem))
|
||
);
|
||
const feeType =
|
||
option?.feeType ||
|
||
fallbackFeeType ||
|
||
(this.isFreightFeeItem(feeItem) ? '物流配送' : '其他费用');
|
||
const key = `${feeType}\u0000${feeItem}`;
|
||
const old = existingGenerated.get(key);
|
||
const current = generatedMap.get(key) || {
|
||
id: old?.id,
|
||
feeType,
|
||
feeItem,
|
||
originalAmount: 0,
|
||
adjustAmount: Number(old?.adjustAmount || 0),
|
||
settlementAmount: 0,
|
||
remark: old?.remark || '',
|
||
manualFlag: 0,
|
||
};
|
||
current.originalAmount = Number((current.originalAmount + amount).toFixed(2));
|
||
current.settlementAmount = Number(
|
||
(current.originalAmount + Number(current.adjustAmount || 0)).toFixed(2)
|
||
);
|
||
generatedMap.set(key, current);
|
||
};
|
||
this.details.forEach(row => {
|
||
const feeItems = this.parseFeeItems(row.feeItemsJson || row.feeItems);
|
||
const entries = Object.entries(feeItems);
|
||
entries.forEach(([feeItem, amount]) => append(feeItem, amount, row.feeType));
|
||
let knownAmount = entries.reduce((total, [, amount]) => total + Number(amount || 0), 0);
|
||
if (!entries.some(([feeItem]) => this.isFreightFeeItem(feeItem))) {
|
||
append('运输费', row.freightAmount, '物流配送');
|
||
knownAmount += Number(row.freightAmount || 0);
|
||
}
|
||
const totalAmount = Number(
|
||
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount ?? 0
|
||
);
|
||
const residualAmount = Number((totalAmount - knownAmount).toFixed(2));
|
||
if (Math.abs(residualAmount) >= 0.005) append('其他费用', residualAmount, '其他费用');
|
||
});
|
||
const manualRows = this.summaryFees.filter(row => row.manualFlag === 1);
|
||
this.summaryFees = [...generatedMap.values(), ...manualRows];
|
||
},
|
||
isFreightFeeItem(name) {
|
||
return name && (name.includes('运费') || name.includes('运输费'));
|
||
},
|
||
handleDetailQuery() {
|
||
this.appliedDetailQuery = { ...this.detailQuery };
|
||
},
|
||
resetDetailQuery(apply = true) {
|
||
this.detailQuery = { documentNo: '', waybillNo: '', batchNo: '', cargoName: '' };
|
||
if (apply) this.handleDetailQuery();
|
||
else this.appliedDetailQuery = { ...this.detailQuery };
|
||
},
|
||
exportDetails() {
|
||
if (!this.details.length) return this.$message.warning('暂无可导出的结算明细');
|
||
const rows = this.details.map((row, index) => {
|
||
const result = { 序号: index + 1 };
|
||
this.detailTableColumns.forEach(column => {
|
||
result[column.label] = column.money
|
||
? Number(row[column.prop] || 0).toFixed(2)
|
||
: row[column.prop] ?? '';
|
||
});
|
||
return result;
|
||
});
|
||
const workbook = XLSX.utils.book_new();
|
||
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(rows), '结算明细');
|
||
XLSX.writeFile(
|
||
workbook,
|
||
`${this.form.formalSettlementNo || '正式结算单'}结算明细${this.$dayjs().format(
|
||
'YYYY-MM-DD HH-mm-ss'
|
||
)}.xlsx`
|
||
);
|
||
},
|
||
async openAdjustDialog(row) {
|
||
this.adjust = {
|
||
visible: true,
|
||
loading: true,
|
||
saving: false,
|
||
detailId: row.id,
|
||
reason: '',
|
||
rows: [],
|
||
};
|
||
try {
|
||
const response = await getDetailFees(row.id);
|
||
const data = this.unwrapData(response);
|
||
this.adjust.rows = (data || []).map(item => ({
|
||
...item,
|
||
feeItems: this.parseFeeItems(item.feeItemsJson),
|
||
}));
|
||
} finally {
|
||
this.adjust.loading = false;
|
||
}
|
||
},
|
||
async saveAdjustment() {
|
||
if (!this.adjust.reason.trim()) return this.$message.warning('请输入调整原因');
|
||
this.adjust.saving = true;
|
||
try {
|
||
await adjustDetail({
|
||
detailId: this.adjust.detailId,
|
||
changeReason: this.adjust.reason,
|
||
rows: this.adjust.rows,
|
||
});
|
||
this.$message.success('调整保存成功');
|
||
this.adjust.visible = false;
|
||
await this.initialize();
|
||
} finally {
|
||
this.adjust.saving = false;
|
||
}
|
||
},
|
||
async handleSave() {
|
||
await this.$refs.formRef.validate();
|
||
if (!this.form.sourcePreSettlementIds.length && !this.form.sourceDetailIds.length) {
|
||
return this.$message.warning('请选择结算明细');
|
||
}
|
||
const invalidManualFee = this.summaryFees.find(
|
||
row =>
|
||
row.manualFlag === 1 &&
|
||
(!String(row.feeType || '').trim() || !String(row.feeItem || '').trim())
|
||
);
|
||
if (invalidManualFee) {
|
||
return this.$message.warning('请完善手工费用的费用类型和费用项');
|
||
}
|
||
this.saving = true;
|
||
try {
|
||
await save({
|
||
id: this.form.id,
|
||
contractId: this.form.contractId,
|
||
settlementType: this.form.settlementType,
|
||
sourcePreSettlementIds: this.form.sourcePreSettlementIds,
|
||
sourceDetailIds: this.form.sourceDetailIds,
|
||
exchangeRateDate: this.form.exchangeRateDate,
|
||
exchangeRate: this.form.exchangeRate,
|
||
summaryFees: this.summaryFees
|
||
.filter(
|
||
row =>
|
||
row.manualFlag === 1 ||
|
||
row.id ||
|
||
Number(row.adjustAmount || 0) !== 0 ||
|
||
String(row.remark || '').trim()
|
||
)
|
||
.map(row => ({
|
||
id: row.id || undefined,
|
||
feeType: row.feeType,
|
||
feeItem: row.feeItem,
|
||
adjustAmount: Number(row.adjustAmount || 0),
|
||
remark: row.remark,
|
||
manualFlag: row.manualFlag || 0,
|
||
})),
|
||
attachmentsJson: JSON.stringify(this.attachments || []),
|
||
remark: this.form.remark,
|
||
});
|
||
this.$message.success('保存成功');
|
||
this.visible = false;
|
||
this.$emit('success');
|
||
} finally {
|
||
this.saving = false;
|
||
}
|
||
},
|
||
displayValue(value) {
|
||
return value === null || value === undefined || value === '' ? '-' : value;
|
||
},
|
||
unwrapData(response) {
|
||
const body = response?.data || response || {};
|
||
return body?.data || body;
|
||
},
|
||
formatMoney(value) {
|
||
return `${Number(value || 0).toFixed(2)} RMB`;
|
||
},
|
||
parseFeeItems(value) {
|
||
if (!value) return {};
|
||
if (typeof value === 'object') return value;
|
||
try {
|
||
return JSON.parse(value);
|
||
} catch {
|
||
return {};
|
||
}
|
||
},
|
||
parseAttachments(value) {
|
||
if (!value) return [];
|
||
if (Array.isArray(value)) return value;
|
||
try {
|
||
const parsed = JSON.parse(value);
|
||
return Array.isArray(parsed) ? parsed : [];
|
||
} catch {
|
||
return [];
|
||
}
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.formal-editor__actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.formal-editor__detail-filter {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 8px 16px;
|
||
margin-bottom: 8px;
|
||
padding: 12px 12px 4px;
|
||
background: #fff;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||
}
|
||
.formal-editor__detail-filter-actions {
|
||
grid-column: 1 / -1;
|
||
justify-self: start;
|
||
}
|
||
.formal-editor__page-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
padding: 16px 0 4px;
|
||
border-top: 1px solid #eff1f7;
|
||
gap: 8px;
|
||
}
|
||
.formal-editor__links {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
}
|
||
.formal-editor :deep(.el-form-item) {
|
||
margin-bottom: 16px;
|
||
}
|
||
.formal-editor :deep(.el-table) {
|
||
--el-table-border-color: #eff1f7;
|
||
}
|
||
.formal-editor :deep(.el-table__body tr:nth-child(even) > td.el-table__cell) {
|
||
background: #fafafa;
|
||
}
|
||
.formal-editor__pagination {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 12px;
|
||
}
|
||
.formal-editor__adjust-reason {
|
||
margin-top: 16px;
|
||
}
|
||
/* 选择预结算单 / 选择结算明细 弹窗搜索区白底 */
|
||
.formal-editor__candidate-search,
|
||
.formal-editor__detail-search {
|
||
background: #fff;
|
||
border-radius: 6px;
|
||
padding: 12px;
|
||
margin-bottom: 16px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.formal-editor__detail-filter {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
</style>
|