Files
tms-erp-web/src/views/payment/bill-payment-form.vue
T
2026-08-27 23:52:17 +08:00

404 lines
12 KiB
Vue

<template>
<basic-container class="bill-payment-form-page">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="right"
label-width="auto"
class="bill-payment-form-page__form"
>
<section-card title="基本信息">
<el-row :gutter="24">
<el-col :span="6">
<el-form-item label="票据号码" prop="billLedgerId">
<el-input
v-model="form.billNo"
readonly
:disabled="readonly"
placeholder="请选择汇票台账"
>
<template #append>
<el-button :icon="Search" :disabled="readonly" @click="openBillDialog" />
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="可用余额">
<el-input :model-value="formatMoney(form.availableBalance)" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="付款日期" prop="paymentDate">
<el-date-picker
v-model="form.paymentDate"
type="date"
value-format="YYYY-MM-DD"
:disabled="readonly"
placeholder="请选择"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="票面金额">
<el-input :model-value="formatMoney(form.faceAmount)" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="本次使用" prop="usedAmount">
<el-input-number
class="used-amount-input"
v-model="form.usedAmount"
:disabled="readonly"
:min="0"
:precision="2"
:controls="false"
placeholder="请输入"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark" class="bill-payment-form-page__remark">
<el-input
v-model="form.remark"
:disabled="readonly"
type="textarea"
:rows="2"
maxlength="200"
show-word-limit
placeholder="请输入"
/>
</el-form-item>
</el-col>
</el-row>
</section-card>
<section-card title="附件信息">
<vehicle-attachment-table v-model="form.attachments" :readonly="readonly" />
</section-card>
<div class="bill-payment-form-page__actions">
<template v-if="!readonly">
<el-button type="primary" :loading="saving" :disabled="submitting" @click="saveDraft">
保存
</el-button>
<el-button type="primary" :loading="submitting" :disabled="saving" @click="submitForm">
提交
</el-button>
</template>
<el-button @click="goBack">取消</el-button>
</div>
</el-form>
<el-dialog v-model="billDialog.visible" title="选择汇票台账" width="86%" append-to-body>
<div class="bill-payment-form-page__dialog-search">
<el-input
v-model="billDialog.keyword"
clearable
placeholder="票据号码、出票单位或收票单位"
@keyup.enter="loadBillOptions"
/>
<el-button type="primary" @click="loadBillOptions">查询</el-button>
</div>
<el-table
v-loading="billDialog.loading"
:data="billDialog.rows"
border
highlight-current-row
@current-change="billDialog.current = $event"
@row-dblclick="selectBill"
>
<el-table-column prop="billNo" label="票据编号" min-width="170" />
<el-table-column prop="issuerName" label="出票单位" min-width="160" />
<el-table-column prop="receiverName" label="收票单位" min-width="160" />
<el-table-column label="票面金额" min-width="130" align="right">
<template #default="{ row }">{{ formatMoney(row.faceAmount) }}</template>
</el-table-column>
<el-table-column label="可用余额" min-width="130" align="right">
<template #default="{ row }">{{ formatMoney(row.availableBalance) }}</template>
</el-table-column>
<el-table-column prop="maturityDate" label="到期日期" min-width="120" />
<el-table-column label="操作" width="100" fixed="right" align="center">
<template #default="{ row }">
<el-link type="primary" @click.stop="selectBill(row)">选择</el-link>
</template>
</el-table-column>
</el-table>
<template #footer>
<el-button @click="billDialog.visible = false">取消</el-button>
<el-button type="primary" @click="selectBill">确定</el-button>
</template>
</el-dialog>
</basic-container>
</template>
<script>
import { Search } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex';
import * as api from '@/api/payment/billPayment';
import * as billLedgerApi from '@/api/payment/billLedger';
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
const emptyForm = () => ({
id: null,
billLedgerId: null,
billNo: '',
faceAmount: 0,
availableBalance: 0,
usedAmount: null,
deptId: null,
deptName: '',
paymentDate: '',
attachments: [],
remark: '',
});
export default {
name: 'BillPaymentForm',
components: { VehicleAttachmentTable },
data() {
return {
Search,
form: emptyForm(),
saving: false,
submitting: false,
billDialog: { visible: false, loading: false, keyword: '', rows: [], current: null },
attachmentFileTypes: [
'pdf',
'bmp',
'jpeg',
'png',
'jpg',
'doc',
'docx',
'ppt',
'pptx',
'xlsx',
'xls',
'eml',
'msg',
'zip',
],
rules: {
billLedgerId: [{ required: true, message: '请选择汇票台账', trigger: 'change' }],
usedAmount: [{ validator: this.validateUsedAmount, trigger: 'change' }],
paymentDate: [{ required: true, message: '请选择付款日期', trigger: 'change' }],
remark: [{ max: 200, message: '备注不能超过200个字符', trigger: 'blur' }],
},
};
},
computed: {
...mapGetters(['userInfo']),
recordId() {
return this.$route.query.id || '';
},
readonly() {
return this.$route.query.mode === 'view';
},
},
created() {
this.initialize();
},
methods: {
unwrapData(response) {
const body = response?.data || response || {};
return body?.data || body;
},
parse(value) {
if (!value) return [];
if (Array.isArray(value)) return value;
try {
return JSON.parse(value) || [];
} catch {
return [];
}
},
async initialize() {
if (this.recordId) {
const data = this.unwrapData(await api.getDetail(this.recordId));
this.form = {
...emptyForm(),
...data,
attachments: this.parse(data.attachmentsJson),
};
return;
}
this.form.deptId = this.userInfo?.deptId || this.userInfo?.dept_id || null;
this.form.deptName = this.userInfo?.deptName || this.userInfo?.dept_name || '';
this.form.paymentDate = this.$dayjs().format('YYYY-MM-DD');
},
validateUsedAmount(rule, value, callback) {
const amount = Number(value);
const balance = Number(this.form.availableBalance || 0);
if (!Number.isFinite(amount) || amount <= 0) {
callback(new Error('本次使用必须大于0'));
return;
}
if (amount > balance) {
callback(new Error('本次使用不能超过汇票可用余额'));
return;
}
callback();
},
async openBillDialog() {
this.billDialog.visible = true;
this.billDialog.keyword = this.form.billNo || '';
await this.loadBillOptions();
},
async loadBillOptions() {
this.billDialog.loading = true;
try {
this.billDialog.rows =
this.unwrapData(
await billLedgerApi.getAvailableOptions(
this.billDialog.keyword,
this.form.deptId,
this.form.billLedgerId
)
) || [];
} finally {
this.billDialog.loading = false;
}
},
selectBill(row) {
const selected = row?.id ? row : this.billDialog.current;
if (!selected) {
this.$message.warning('请选择汇票台账');
return;
}
Object.assign(this.form, {
billLedgerId: selected.id,
billNo: selected.billNo,
faceAmount: selected.faceAmount,
availableBalance: selected.availableBalance,
});
this.billDialog.visible = false;
this.$refs.formRef?.validateField('billLedgerId').catch(() => {});
this.$refs.formRef?.validateField('usedAmount').catch(() => {});
},
normalizeAttachments(files) {
const userName = this.userInfo?.realName || this.userInfo?.userName || '';
const time = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
this.form.attachments = (files || []).map(file => ({
...file,
description: file.description || '',
uploadUserName: file.uploadUserName || userName,
uploadTime: file.uploadTime || time,
}));
},
fileSize(file) {
if (typeof file.size === 'string' && /[a-z]/i.test(file.size)) return file.size;
const bytes = Number(file.size || file.fileSize || 0);
if (!bytes) return '-';
if (bytes < 1024) return `${bytes}B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
},
payload() {
return {
id: this.form.id,
billLedgerId: this.form.billLedgerId,
usedAmount: this.form.usedAmount,
paymentDate: this.form.paymentDate,
attachmentsJson: JSON.stringify(this.form.attachments || []),
remark: this.form.remark,
};
},
async saveDraft() {
await this.$refs.formRef.validate();
this.saving = true;
try {
const id = this.unwrapData(await api.save(this.payload()));
this.form.id = id;
this.$message.success('保存成功');
return true;
} finally {
this.saving = false;
}
},
async submitForm() {
await this.$refs.formRef.validate();
this.submitting = true;
try {
const id = this.unwrapData(await api.save(this.payload()));
this.form.id = id;
await api.submit({ id: this.form.id });
this.$message.success('提交成功');
this.goBack();
} finally {
this.submitting = false;
}
},
goBack() {
this.$router.push('/payment/bill-payment');
},
formatMoney(value) {
return Number(value || 0).toFixed(2);
},
},
};
</script>
<style scoped lang="scss">
.bill-payment-form-page__form :deep(.el-form-item) {
margin-bottom: 14px;
}
.bill-payment-form-page__form :deep(.el-input),
.bill-payment-form-page__form :deep(.el-input-number),
.bill-payment-form-page__form :deep(.el-date-editor) {
width: 100%;
}
.bill-payment-form-page__remark :deep(.el-form-item__content) {
margin-left: 0 !important;
}
// 本次使用 input-number 默认文字居中,改为居左
.used-amount-input :deep(.el-input__inner) {
text-align: left;
}
.bill-payment-form-page__uploader {
margin-top: 12px;
}
.bill-payment-form-page__actions {
display: flex;
justify-content: flex-end;
padding: 12px 24px;
position: fixed;
right: 0;
left: 230px;
bottom: 0;
margin: 0;
z-index: 10;
background: #fff;
border-top: 1px solid #eff1f7;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
}
:global(.avue--collapse .bill-payment-form-page__actions) {
left: 60px;
}
:global(.avue-layout--horizontal .bill-payment-form-page__actions) {
left: 0;
}
.bill-payment-form-page__dialog-search {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-bottom: 12px;
}
.bill-payment-form-page__dialog-search .el-input {
width: 360px;
}
.bill-payment-form-page :deep(.el-table) {
--el-table-border-color: #eff1f7;
}
.bill-payment-form-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
background: #fafafa;
}
@media (max-width: 1024px) {
.bill-payment-form-page :deep(.el-col-6) {
max-width: 100%;
flex: 0 0 100%;
}
}
</style>