1、调整配载、总单
2、新增收付款模块
This commit is contained in:
@@ -0,0 +1,599 @@
|
||||
<template>
|
||||
<basic-container class="bill-ledger-form-page">
|
||||
<div class="bill-ledger-form-page__title">
|
||||
{{ recordId ? '编辑汇票台账' : '新增汇票台账' }}
|
||||
</div>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="bill-ledger-form-page__form"
|
||||
>
|
||||
<section class="bill-ledger-form-page__section">
|
||||
<div class="bill-ledger-form-page__section-title">基本信息</div>
|
||||
<el-row :gutter="48">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="票据号码" prop="billNo">
|
||||
<el-input
|
||||
v-model="form.billNo"
|
||||
:disabled="Boolean(recordId)"
|
||||
maxlength="32"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="出票单位" prop="issuerId">
|
||||
<el-select
|
||||
v-model="form.issuerId"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="searchCustomers"
|
||||
:loading="customerLoading"
|
||||
placeholder="请选择客商基础档案"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in customerOptions"
|
||||
:key="item.id"
|
||||
:label="customerName(item)"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="票面金额" prop="faceAmount">
|
||||
<el-input-number
|
||||
v-model="form.faceAmount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="出票日期" prop="issueDate">
|
||||
<el-date-picker
|
||||
v-model="form.issueDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="可用部门" prop="availableDeptIds">
|
||||
<el-tree-select
|
||||
v-model="form.availableDeptIds"
|
||||
:data="deptTree"
|
||||
:props="deptProps"
|
||||
node-key="id"
|
||||
multiple
|
||||
show-checkbox
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="请选择,默认全部"
|
||||
@change="handleDeptChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="费用承担方" prop="feeBearerId">
|
||||
<el-select
|
||||
v-model="form.feeBearerId"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="searchCustomers"
|
||||
:loading="customerLoading"
|
||||
placeholder="请选择客商基础档案"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in customerOptions"
|
||||
:key="item.id"
|
||||
:label="customerName(item)"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="双方确认贴现率" prop="confirmedDiscountRate">
|
||||
<el-input-number
|
||||
v-model="form.confirmedDiscountRate"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="4"
|
||||
:controls="false"
|
||||
placeholder="请输入"
|
||||
>
|
||||
<template #suffix>%</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="汇票类型" prop="billType">
|
||||
<el-select v-model="form.billType" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in billTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="收票单位" prop="receiverName">
|
||||
<el-select
|
||||
v-model="form.receiverName"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="请选择或输入"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in receiverOptions"
|
||||
:key="item"
|
||||
:label="item"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="可用余额">
|
||||
<el-input :model-value="formatMoney(projectedAvailableBalance)" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="到期日期" prop="maturityDate">
|
||||
<el-date-picker
|
||||
v-model="form.maturityDate"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="出票行" prop="issuingBank">
|
||||
<el-select
|
||||
v-model="form.issuingBank"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="请选择或输入"
|
||||
>
|
||||
<el-option v-for="item in bankOptions" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="银行贴现参考率" prop="bankDiscountReferenceRate">
|
||||
<el-input-number
|
||||
v-model="form.bankDiscountReferenceRate"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="4"
|
||||
:controls="false"
|
||||
placeholder="请输入"
|
||||
>
|
||||
<template #suffix>%</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="预计贴现费用">
|
||||
<el-input :model-value="formatMoney(estimatedDiscountFee)" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
|
||||
<section class="bill-ledger-form-page__section">
|
||||
<div class="bill-ledger-form-page__section-title">附件信息</div>
|
||||
<el-table :data="form.attachments" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="文件名" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<el-link
|
||||
v-if="row.url || row.link"
|
||||
type="primary"
|
||||
:href="row.url || row.link"
|
||||
target="_blank"
|
||||
>
|
||||
{{ row.originalName || row.name || '附件' }}
|
||||
</el-link>
|
||||
<span v-else>{{ row.originalName || row.name || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="附件描述" min-width="240">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.description" maxlength="200" placeholder="请输入" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件大小" width="120" align="center">
|
||||
<template #default="{ row }">{{ fileSize(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploadUserName" label="上传人" width="130" align="center" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="170" align="center" />
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-link type="danger" @click="form.attachments.splice($index, 1)">删除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<vehicle-attachment-upload
|
||||
v-model="form.attachments"
|
||||
class="bill-ledger-form-page__uploader"
|
||||
:multiple="true"
|
||||
:limit="20"
|
||||
:max-size="10"
|
||||
:file-types="attachmentFileTypes"
|
||||
:show-file-list="false"
|
||||
button-text="上传"
|
||||
@change="normalizeAttachments"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="bill-ledger-form-page__section">
|
||||
<div class="bill-ledger-form-page__section-title">备注</div>
|
||||
<el-form-item prop="remark" class="bill-ledger-form-page__remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<section class="bill-ledger-form-page__section">
|
||||
<div class="bill-ledger-form-page__section-title">使用记录</div>
|
||||
<el-table :data="usageRecords" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column prop="applicationNo" label="申请单号" min-width="180" align="center" />
|
||||
<el-table-column label="使用金额" min-width="150" align="center">
|
||||
<template #default="{ row }">{{ formatMoney(row.usedAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="useDeptName" label="使用部门" min-width="180" align="center" />
|
||||
<el-table-column label="状态" min-width="120" align="center">
|
||||
<template #default>已审核</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<div class="bill-ledger-form-page__actions">
|
||||
<el-button type="primary" :loading="saving" @click="confirmSubmit">确认</el-button>
|
||||
<el-button @click="goBack">取消</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/billLedger';
|
||||
import { getList as getCustomerList } from '@/api/vehicle/customer-archive';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
billNo: '',
|
||||
issuerId: null,
|
||||
receiverName: '',
|
||||
billType: 'issued',
|
||||
faceAmount: null,
|
||||
availableBalance: 0,
|
||||
issueDate: '',
|
||||
maturityDate: '',
|
||||
availableDeptIds: ['all'],
|
||||
availableDeptNames: '全部',
|
||||
feeBearerId: null,
|
||||
confirmedDiscountRate: null,
|
||||
issuingBank: '',
|
||||
bankDiscountReferenceRate: null,
|
||||
attachments: [],
|
||||
remark: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'BillLedgerForm',
|
||||
data() {
|
||||
return {
|
||||
form: emptyForm(),
|
||||
usageRecords: [],
|
||||
billTypeOptions: api.billTypeOptions,
|
||||
customerOptions: [],
|
||||
receiverOptions: [],
|
||||
customerLoading: false,
|
||||
saving: false,
|
||||
deptTree: [],
|
||||
deptMap: new Map(),
|
||||
deptProps: { label: 'title', value: 'id', children: 'children' },
|
||||
bankOptions: [
|
||||
'中国工商银行',
|
||||
'中国农业银行',
|
||||
'中国银行',
|
||||
'中国建设银行',
|
||||
'交通银行',
|
||||
'招商银行',
|
||||
'浦发银行',
|
||||
'中信银行',
|
||||
],
|
||||
attachmentFileTypes: [
|
||||
'pdf',
|
||||
'bmp',
|
||||
'jpeg',
|
||||
'png',
|
||||
'jpg',
|
||||
'doc',
|
||||
'docx',
|
||||
'ppt',
|
||||
'pptx',
|
||||
'xlsx',
|
||||
'xls',
|
||||
'eml',
|
||||
'msg',
|
||||
'zip',
|
||||
],
|
||||
rules: {
|
||||
billNo: [
|
||||
{ required: true, message: '请输入票据号码', trigger: 'blur' },
|
||||
{ max: 32, message: '票据号码不能超过32个字符', trigger: 'blur' },
|
||||
],
|
||||
issuerId: [{ required: true, message: '请选择出票单位', trigger: 'change' }],
|
||||
receiverName: [{ required: true, message: '请选择或输入收票单位', trigger: 'change' }],
|
||||
billType: [{ required: true, message: '请选择汇票类型', trigger: 'change' }],
|
||||
faceAmount: [{ validator: this.validateFaceAmount, trigger: 'change' }],
|
||||
issueDate: [{ required: true, message: '请选择出票日期', trigger: 'change' }],
|
||||
maturityDate: [{ validator: this.validateMaturityDate, trigger: 'change' }],
|
||||
availableDeptIds: [{ validator: this.validateDepartments, trigger: 'change' }],
|
||||
feeBearerId: [{ required: true, message: '请选择费用承担方', trigger: 'change' }],
|
||||
confirmedDiscountRate: [{ validator: this.validateRate, trigger: 'change' }],
|
||||
issuingBank: [
|
||||
{ required: true, message: '请选择或输入出票行', trigger: 'change' },
|
||||
{ max: 100, message: '出票行不能超过100个字符', trigger: 'change' },
|
||||
],
|
||||
bankDiscountReferenceRate: [{ validator: this.validateRate, trigger: 'change' }],
|
||||
remark: [{ max: 200, message: '备注不能超过200个字符', trigger: 'blur' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo']),
|
||||
recordId() {
|
||||
return this.$route.query.id || '';
|
||||
},
|
||||
activeUsedAmount() {
|
||||
return this.usageRecords.reduce((total, item) => total + Number(item.usedAmount || 0), 0);
|
||||
},
|
||||
projectedAvailableBalance() {
|
||||
return Math.max(Number(this.form.faceAmount || 0) - this.activeUsedAmount, 0);
|
||||
},
|
||||
estimatedDiscountFee() {
|
||||
return (
|
||||
(Number(this.form.faceAmount || 0) * Number(this.form.confirmedDiscountRate || 0)) / 100
|
||||
);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initialize();
|
||||
},
|
||||
methods: {
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
async initialize() {
|
||||
await Promise.all([this.loadDepartments(), this.searchCustomers('')]);
|
||||
if (!this.recordId) {
|
||||
this.form.issueDate = this.$dayjs().format('YYYY-MM-DD');
|
||||
return;
|
||||
}
|
||||
const data = this.unwrapData(await api.getDetail(this.recordId));
|
||||
this.form = {
|
||||
...emptyForm(),
|
||||
...data,
|
||||
availableDeptIds: this.parse(data.availableDeptIdsJson),
|
||||
attachments: this.parse(data.attachmentsJson),
|
||||
};
|
||||
this.usageRecords = data.usageRecords || [];
|
||||
this.receiverOptions = data.receiverName ? [data.receiverName] : [];
|
||||
this.ensureCustomerOption(data.issuerId, data.issuerName);
|
||||
this.ensureCustomerOption(data.feeBearerId, data.feeBearerName);
|
||||
this.handleDeptChange(this.form.availableDeptIds);
|
||||
},
|
||||
parse(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value;
|
||||
try {
|
||||
return JSON.parse(value) || [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
async loadDepartments() {
|
||||
const response = await getDeptTree(this.userInfo?.tenantId);
|
||||
const rows = this.unwrapData(response) || [];
|
||||
this.deptTree = [{ id: 'all', title: '全部', children: rows }];
|
||||
this.deptMap = new Map([['all', '全部']]);
|
||||
this.flattenDepartments(rows).forEach(item => this.deptMap.set(String(item.id), item.title));
|
||||
},
|
||||
flattenDepartments(rows) {
|
||||
return (rows || []).flatMap(item => [item, ...this.flattenDepartments(item.children || [])]);
|
||||
},
|
||||
handleDeptChange(values) {
|
||||
const ids = values || [];
|
||||
this.form.availableDeptNames = ids.some(item => String(item) === 'all')
|
||||
? '全部'
|
||||
: ids
|
||||
.map(item => this.deptMap.get(String(item)))
|
||||
.filter(Boolean)
|
||||
.join('、');
|
||||
},
|
||||
async searchCustomers(keyword) {
|
||||
this.customerLoading = true;
|
||||
try {
|
||||
const data = this.unwrapData(
|
||||
await getCustomerList(1, 100, { approvalStatus: 'approved', fullName: keyword || '' })
|
||||
);
|
||||
const current = this.customerOptions.filter(item =>
|
||||
[this.form.issuerId, this.form.feeBearerId].some(id => String(id) === String(item.id))
|
||||
);
|
||||
this.customerOptions = [...current, ...(data.records || [])].filter(
|
||||
(item, index, rows) => rows.findIndex(row => String(row.id) === String(item.id)) === index
|
||||
);
|
||||
this.receiverOptions = [
|
||||
...new Set(this.customerOptions.map(item => this.customerName(item)).filter(Boolean)),
|
||||
];
|
||||
} finally {
|
||||
this.customerLoading = false;
|
||||
}
|
||||
},
|
||||
ensureCustomerOption(id, name) {
|
||||
if (!id || this.customerOptions.some(item => String(item.id) === String(id))) return;
|
||||
this.customerOptions.push({ id, fullName: name, shortName: name });
|
||||
},
|
||||
customerName(item) {
|
||||
return item.fullName || item.shortName || item.customerCode || '-';
|
||||
},
|
||||
validateFaceAmount(rule, value, callback) {
|
||||
if (!Number.isFinite(Number(value)) || Number(value) <= 0) {
|
||||
callback(new Error('票面金额必须大于0'));
|
||||
return;
|
||||
}
|
||||
if (Number(value) < this.activeUsedAmount) {
|
||||
callback(new Error('票面金额不能小于已使用金额'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateMaturityDate(rule, value, callback) {
|
||||
if (!value) {
|
||||
callback(new Error('请选择到期日期'));
|
||||
return;
|
||||
}
|
||||
if (this.form.issueDate && !this.$dayjs(value).isAfter(this.$dayjs(this.form.issueDate))) {
|
||||
callback(new Error('到期日期必须晚于出票日期'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateDepartments(rule, value, callback) {
|
||||
if (!Array.isArray(value) || !value.length) {
|
||||
callback(new Error('请选择可用部门'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateRate(rule, value, callback) {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
const rate = Number(value);
|
||||
if (!Number.isFinite(rate) || rate < 0 || rate > 100) {
|
||||
callback(new Error('贴现率必须在0-100之间'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback();
|
||||
},
|
||||
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,
|
||||
billNo: this.form.billNo,
|
||||
issuerId: this.form.issuerId,
|
||||
receiverName: this.form.receiverName,
|
||||
billType: this.form.billType,
|
||||
faceAmount: this.form.faceAmount,
|
||||
issueDate: this.form.issueDate,
|
||||
maturityDate: this.form.maturityDate,
|
||||
availableDeptIdsJson: JSON.stringify(this.form.availableDeptIds || []),
|
||||
availableDeptNames: this.form.availableDeptNames,
|
||||
feeBearerId: this.form.feeBearerId,
|
||||
confirmedDiscountRate: this.form.confirmedDiscountRate,
|
||||
issuingBank: this.form.issuingBank,
|
||||
bankDiscountReferenceRate: this.form.bankDiscountReferenceRate,
|
||||
attachmentsJson: JSON.stringify(this.form.attachments || []),
|
||||
remark: this.form.remark,
|
||||
};
|
||||
},
|
||||
async confirmSubmit() {
|
||||
await this.$refs.formRef.validate();
|
||||
this.saving = true;
|
||||
try {
|
||||
await api.submit(this.payload());
|
||||
this.$message.success(this.recordId ? '编辑成功' : '新增成功');
|
||||
this.goBack();
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/payment/bill-ledger');
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bill-ledger-form-page__title,
|
||||
.bill-ledger-form-page__section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.bill-ledger-form-page__title::before,
|
||||
.bill-ledger-form-page__section-title::before {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
.bill-ledger-form-page__section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.bill-ledger-form-page__section-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.bill-ledger-form-page__form :deep(.el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.bill-ledger-form-page__form :deep(.el-input),
|
||||
.bill-ledger-form-page__form :deep(.el-select),
|
||||
.bill-ledger-form-page__form :deep(.el-input-number),
|
||||
.bill-ledger-form-page__form :deep(.el-date-editor),
|
||||
.bill-ledger-form-page__form :deep(.el-tree-select) {
|
||||
width: 100%;
|
||||
}
|
||||
.bill-ledger-form-page__remark :deep(.el-form-item__content) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.bill-ledger-form-page__uploader {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.bill-ledger-form-page__actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 16px 0 8px;
|
||||
}
|
||||
.bill-ledger-form-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.bill-ledger-form-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
.bill-ledger-form-page :deep(.el-col-12) {
|
||||
max-width: 100%;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<basic-container class="bill-ledger-page">
|
||||
<bill-ledger-search
|
||||
:query="query"
|
||||
:counts="counts"
|
||||
:loading="loading"
|
||||
@search="handleSearch"
|
||||
@reset="resetSearch"
|
||||
/>
|
||||
<bill-ledger-table
|
||||
:rows="rows"
|
||||
:loading="loading"
|
||||
:page="page"
|
||||
@add="openCreate"
|
||||
@edit="openEdit"
|
||||
@delete="handleDelete"
|
||||
@page-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as api from '@/api/payment/billLedger';
|
||||
import BillLedgerSearch from './components/bill-ledger-search.vue';
|
||||
import BillLedgerTable from './components/bill-ledger-table.vue';
|
||||
|
||||
const emptyQuery = () => ({
|
||||
billNo: '',
|
||||
issueDateRange: [],
|
||||
issuerName: '',
|
||||
receiverName: '',
|
||||
billType: '',
|
||||
maturityStatus: '',
|
||||
expiryShortcut: 'all',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'BillLedger',
|
||||
components: { BillLedgerSearch, BillLedgerTable },
|
||||
data() {
|
||||
return {
|
||||
query: emptyQuery(),
|
||||
counts: { all: 0, within30: 0, within90: 0, over90: 0 },
|
||||
rows: [],
|
||||
loading: false,
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
requestParams() {
|
||||
const { issueDateRange, ...params } = this.query;
|
||||
return {
|
||||
...params,
|
||||
issueStartDate: issueDateRange?.[0] || '',
|
||||
issueEndDate: issueDateRange?.[1] || '',
|
||||
};
|
||||
},
|
||||
async loadData() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const [listResponse, countResponse] = await Promise.all([
|
||||
api.getList(this.page.current, this.page.size, this.requestParams()),
|
||||
api.getExpiryCounts(),
|
||||
]);
|
||||
const data = this.unwrapData(listResponse);
|
||||
this.rows = data.records || [];
|
||||
this.page.total = Number(data.total || 0);
|
||||
this.counts = { ...this.counts, ...(this.unwrapData(countResponse) || {}) };
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.page.current = 1;
|
||||
this.loadData();
|
||||
},
|
||||
resetSearch() {
|
||||
this.query = emptyQuery();
|
||||
this.handleSearch();
|
||||
},
|
||||
openCreate() {
|
||||
this.$router.push({ path: '/payment/bill-ledger/form', query: { mode: 'add' } });
|
||||
},
|
||||
openEdit(row) {
|
||||
this.$router.push({
|
||||
path: '/payment/bill-ledger/form',
|
||||
query: { mode: 'edit', id: row.id },
|
||||
});
|
||||
},
|
||||
async handleDelete(row) {
|
||||
await this.$confirm('确认删除该汇票台账?', '提示', { type: 'warning' });
|
||||
await api.remove(row.id);
|
||||
this.$message.success('删除成功');
|
||||
if (this.rows.length === 1 && this.page.current > 1) this.page.current -= 1;
|
||||
this.loadData();
|
||||
},
|
||||
handlePageChange(current) {
|
||||
this.page.current = current;
|
||||
this.loadData();
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.page.current = 1;
|
||||
this.page.size = size;
|
||||
this.loadData();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,434 @@
|
||||
<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 class="bill-payment-form-page__section">
|
||||
<div class="bill-payment-form-page__section-title">基本信息</div>
|
||||
<el-row :gutter="48">
|
||||
<el-col :span="12">
|
||||
<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-form-item label="可用余额">
|
||||
<el-input :model-value="formatMoney(form.availableBalance)" disabled />
|
||||
</el-form-item>
|
||||
<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="12">
|
||||
<el-form-item label="票面金额">
|
||||
<el-input :model-value="formatMoney(form.faceAmount)" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="本次使用" prop="usedAmount">
|
||||
<el-input-number
|
||||
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="3"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
|
||||
<section class="bill-payment-form-page__section">
|
||||
<div class="bill-payment-form-page__section-title">附件信息</div>
|
||||
<el-table :data="form.attachments" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="文件名" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<el-link
|
||||
v-if="row.url || row.link"
|
||||
type="primary"
|
||||
:href="row.url || row.link"
|
||||
target="_blank"
|
||||
>
|
||||
{{ row.originalName || row.name || '附件' }}
|
||||
</el-link>
|
||||
<span v-else>{{ row.originalName || row.name || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="附件描述" min-width="240">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.description" :disabled="readonly" maxlength="200" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件大小" width="120" align="center">
|
||||
<template #default="{ row }">{{ fileSize(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploadUserName" label="上传人" width="130" align="center" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="170" align="center" />
|
||||
<el-table-column label="操作" width="100" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-link v-if="!readonly" type="danger" @click="form.attachments.splice($index, 1)"
|
||||
>删除</el-link
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<vehicle-attachment-upload
|
||||
v-if="!readonly"
|
||||
v-model="form.attachments"
|
||||
class="bill-payment-form-page__uploader"
|
||||
:multiple="true"
|
||||
:limit="20"
|
||||
:max-size="10"
|
||||
:file-types="attachmentFileTypes"
|
||||
:show-file-list="false"
|
||||
button-text="上传"
|
||||
@change="normalizeAttachments"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
<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';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
billLedgerId: null,
|
||||
billNo: '',
|
||||
faceAmount: 0,
|
||||
availableBalance: 0,
|
||||
usedAmount: null,
|
||||
deptId: null,
|
||||
deptName: '',
|
||||
paymentDate: '',
|
||||
attachments: [],
|
||||
remark: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'BillPaymentForm',
|
||||
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__section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.bill-payment-form-page__section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.bill-payment-form-page__section-title::before {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
.bill-payment-form-page__form :deep(.el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.bill-payment-form-page__uploader {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.bill-payment-form-page__actions {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
padding: 16px 0 8px;
|
||||
}
|
||||
.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-12) {
|
||||
max-width: 100%;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<basic-container class="bill-payment-page">
|
||||
<bill-payment-search
|
||||
:query="query"
|
||||
:loading="loading"
|
||||
@search="handleSearch"
|
||||
@reset="resetSearch"
|
||||
/>
|
||||
<bill-payment-table
|
||||
:rows="rows"
|
||||
:loading="loading"
|
||||
:page="page"
|
||||
@add="openCreate"
|
||||
@view="openView"
|
||||
@edit="openEdit"
|
||||
@delete="handleDelete"
|
||||
@flow="openFlow"
|
||||
@void="handleVoid"
|
||||
@page-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
<el-dialog v-model="flowDialog.visible" title="审批流程" width="620px" append-to-body>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="单据号">
|
||||
{{ flowDialog.row.paymentNo || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单据状态">
|
||||
{{ flowDialog.row.approvalStatusName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前节点">
|
||||
{{ flowDialog.row.currentNode || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前处理人">
|
||||
{{ flowDialog.row.currentProcessor || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as api from '@/api/payment/billPayment';
|
||||
import BillPaymentSearch from './components/bill-payment-search.vue';
|
||||
import BillPaymentTable from './components/bill-payment-table.vue';
|
||||
|
||||
const emptyQuery = () => ({
|
||||
paymentNo: '',
|
||||
deptName: '',
|
||||
paymentDateRange: [],
|
||||
approvalStatus: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'BillPayment',
|
||||
components: { BillPaymentSearch, BillPaymentTable },
|
||||
data() {
|
||||
return {
|
||||
query: emptyQuery(),
|
||||
rows: [],
|
||||
loading: false,
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
flowDialog: { visible: false, row: {} },
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadTable();
|
||||
},
|
||||
methods: {
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
buildParams() {
|
||||
const params = { ...this.query };
|
||||
const range = params.paymentDateRange || [];
|
||||
delete params.paymentDateRange;
|
||||
if (range.length === 2) {
|
||||
params.paymentStartDate = range[0];
|
||||
params.paymentEndDate = range[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
async loadTable() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const data = this.unwrapData(
|
||||
await api.getList(this.page.current, this.page.size, this.buildParams())
|
||||
);
|
||||
this.rows = data.records || [];
|
||||
this.page.total = Number(data.total || 0);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
resetSearch() {
|
||||
this.query = emptyQuery();
|
||||
this.handleSearch();
|
||||
},
|
||||
openCreate() {
|
||||
this.$router.push({ path: '/payment/bill-payment/form', query: { mode: 'add' } });
|
||||
},
|
||||
openView(row) {
|
||||
this.$router.push({
|
||||
path: '/payment/bill-payment/form',
|
||||
query: { mode: 'view', id: row.id },
|
||||
});
|
||||
},
|
||||
openEdit(row) {
|
||||
this.$router.push({
|
||||
path: '/payment/bill-payment/form',
|
||||
query: { mode: 'edit', id: row.id },
|
||||
});
|
||||
},
|
||||
async handleDelete(row) {
|
||||
await this.$confirm('确认删除该汇票付款?', '提示', { type: 'warning' });
|
||||
await api.remove(row.id);
|
||||
this.$message.success('删除成功');
|
||||
if (this.rows.length === 1 && this.page.current > 1) this.page.current -= 1;
|
||||
this.loadTable();
|
||||
},
|
||||
openFlow(row) {
|
||||
this.flowDialog.row = row;
|
||||
this.flowDialog.visible = true;
|
||||
},
|
||||
async handleVoid(row) {
|
||||
const { value } = await this.$prompt('请输入作废原因', '作废汇票付款');
|
||||
await api.voidBill({ id: row.id, reason: value });
|
||||
this.$message.success('作废成功');
|
||||
this.loadTable();
|
||||
},
|
||||
handlePageChange(current) {
|
||||
this.page.current = current;
|
||||
this.loadTable();
|
||||
},
|
||||
handleSizeChange(size) {
|
||||
this.page.current = 1;
|
||||
this.page.size = size;
|
||||
this.loadTable();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<section class="bill-ledger-search">
|
||||
<el-form :model="query" label-position="right" label-width="160px" @submit.prevent>
|
||||
<div class="bill-ledger-search__grid">
|
||||
<el-form-item label="票据编号">
|
||||
<el-input v-model="query.billNo" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="出票日期">
|
||||
<el-date-picker
|
||||
v-model="query.issueDateRange"
|
||||
type="daterange"
|
||||
unlink-panels
|
||||
clearable
|
||||
value-format="YYYY-MM-DD"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="出票单位">
|
||||
<el-input v-model="query.issuerName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="收票单位">
|
||||
<el-input v-model="query.receiverName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="expanded" label="汇票类型">
|
||||
<el-select v-model="query.billType" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in billTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="expanded" label="到期状态">
|
||||
<el-select v-model="query.maturityStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in maturityStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="bill-ledger-search__shortcut-row">
|
||||
<span class="bill-ledger-search__shortcut-label">到期快捷筛选</span>
|
||||
<el-segmented v-model="query.expiryShortcut" :options="shortcutOptions" />
|
||||
</div>
|
||||
<div class="bill-ledger-search__actions">
|
||||
<el-button type="primary" :loading="loading" @click="$emit('search')">查询</el-button>
|
||||
<el-button @click="$emit('reset')">重置</el-button>
|
||||
<el-tooltip :content="expanded ? '折叠' : '展开'" placement="top">
|
||||
<el-button text @click="expanded = !expanded">
|
||||
<el-icon><component :is="expanded ? ArrowUp : ArrowDown" /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||
import { billTypeOptions, maturityStatusOptions } from '@/api/payment/billLedger';
|
||||
|
||||
export default {
|
||||
name: 'BillLedgerSearch',
|
||||
props: {
|
||||
query: { type: Object, required: true },
|
||||
counts: { type: Object, default: () => ({}) },
|
||||
loading: { type: Boolean, default: false },
|
||||
},
|
||||
emits: ['search', 'reset'],
|
||||
data() {
|
||||
return {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
expanded: false,
|
||||
billTypeOptions,
|
||||
maturityStatusOptions,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
shortcutOptions() {
|
||||
return [
|
||||
{ label: `全部(${this.counts.all || 0})`, value: 'all' },
|
||||
{ label: `30天内到期(${this.counts.within30 || 0})`, value: 'within30' },
|
||||
{ label: `90天内到期(${this.counts.within90 || 0})`, value: 'within90' },
|
||||
{ label: `90天以上(${this.counts.over90 || 0})`, value: 'over90' },
|
||||
];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bill-ledger-search {
|
||||
padding: 12px 12px 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.bill-ledger-search__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px 24px;
|
||||
}
|
||||
.bill-ledger-search :deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.bill-ledger-search :deep(.el-input),
|
||||
.bill-ledger-search :deep(.el-select),
|
||||
.bill-ledger-search :deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
}
|
||||
.bill-ledger-search__shortcut-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 40px;
|
||||
}
|
||||
.bill-ledger-search__shortcut-label {
|
||||
width: 160px;
|
||||
padding-right: 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.bill-ledger-search__shortcut-row :deep(.el-segmented) {
|
||||
--el-segmented-item-selected-bg-color: #409eff;
|
||||
--el-segmented-item-selected-color: #fff;
|
||||
}
|
||||
.bill-ledger-search__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.bill-ledger-search__grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.bill-ledger-search__shortcut-row {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.bill-ledger-search__shortcut-row :deep(.el-segmented) {
|
||||
max-width: calc(100% - 160px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<section class="bill-ledger-table">
|
||||
<div class="bill-ledger-table__toolbar">
|
||||
<div class="bill-ledger-table__title">汇票台账列表</div>
|
||||
<el-button v-if="hasPermission('bill_ledger_add')" type="primary" @click="$emit('add')">
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="rows" border>
|
||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in columns"
|
||||
: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 label="操作" width="220" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="bill-ledger-table__links">
|
||||
<el-link
|
||||
v-if="hasPermission('bill_ledger_edit')"
|
||||
type="primary"
|
||||
@click="$emit('edit', row)"
|
||||
>
|
||||
编辑
|
||||
</el-link>
|
||||
<el-link
|
||||
v-if="hasPermission('bill_ledger_delete')"
|
||||
type="danger"
|
||||
@click="$emit('delete', row)"
|
||||
>
|
||||
删除
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="bill-ledger-table__pagination">
|
||||
<el-pagination
|
||||
:current-page="page.current"
|
||||
:page-size="page.size"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="$emit('page-change', $event)"
|
||||
@size-change="$emit('size-change', $event)"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { billLedgerTableColumns } from '@/option/payment/billLedger';
|
||||
|
||||
export default {
|
||||
name: 'BillLedgerTable',
|
||||
props: {
|
||||
rows: { type: Array, default: () => [] },
|
||||
loading: { type: Boolean, default: false },
|
||||
page: { type: Object, required: true },
|
||||
},
|
||||
emits: ['add', 'edit', 'delete', 'page-change', 'size-change'],
|
||||
data() {
|
||||
return { columns: billLedgerTableColumns };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.permission?.[code] !== false;
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : value;
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bill-ledger-table {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.bill-ledger-table__toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.bill-ledger-table__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.bill-ledger-table__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.bill-ledger-table__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.bill-ledger-table :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.bill-ledger-table :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<section class="bill-payment-search">
|
||||
<el-form :model="query" label-position="right" label-width="160px" @submit.prevent>
|
||||
<div class="bill-payment-search__grid">
|
||||
<el-form-item label="单据号">
|
||||
<el-input v-model="query.paymentNo" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="使用部门">
|
||||
<el-input v-model="query.deptName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="付款日期">
|
||||
<el-date-picker
|
||||
v-model="query.paymentDateRange"
|
||||
type="daterange"
|
||||
unlink-panels
|
||||
clearable
|
||||
value-format="YYYY-MM-DD"
|
||||
range-separator="~"
|
||||
start-placeholder="年/月/日"
|
||||
end-placeholder="年/月/日"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="单据状态">
|
||||
<el-select v-model="query.approvalStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in approvalStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="bill-payment-search__actions">
|
||||
<el-button type="primary" :loading="loading" @click="$emit('search')">查询</el-button>
|
||||
<el-button @click="$emit('reset')">重置</el-button>
|
||||
<el-tooltip :content="expanded ? '折叠' : '展开'" placement="top">
|
||||
<el-button text @click="expanded = !expanded">
|
||||
<el-icon><component :is="expanded ? ArrowUp : ArrowDown" /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||
import { approvalStatusOptions } from '@/api/payment/billPayment';
|
||||
|
||||
export default {
|
||||
name: 'BillPaymentSearch',
|
||||
props: {
|
||||
query: { type: Object, required: true },
|
||||
loading: { type: Boolean, default: false },
|
||||
},
|
||||
emits: ['search', 'reset'],
|
||||
data() {
|
||||
return { ArrowDown, ArrowUp, expanded: false, approvalStatusOptions };
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bill-payment-search {
|
||||
padding: 12px 12px 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.bill-payment-search__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px 24px;
|
||||
}
|
||||
.bill-payment-search :deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.bill-payment-search :deep(.el-input),
|
||||
.bill-payment-search :deep(.el-select),
|
||||
.bill-payment-search :deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
}
|
||||
.bill-payment-search__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.bill-payment-search__grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<section class="bill-payment-table">
|
||||
<div class="bill-payment-table__toolbar">
|
||||
<el-button v-if="hasPermission('bill_payment_add')" type="primary" @click="$emit('add')">
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="rows" border>
|
||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in columns"
|
||||
:key="column.prop"
|
||||
v-bind="column"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="column.status" :type="statusType(row.approvalStatus)">
|
||||
{{ row[column.prop] || '-' }}
|
||||
</el-tag>
|
||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="bill-payment-table__links">
|
||||
<el-link
|
||||
v-if="
|
||||
['reviewing', 'approved', 'voided'].includes(row.approvalStatus) &&
|
||||
hasPermission('bill_payment_view')
|
||||
"
|
||||
type="primary"
|
||||
@click="$emit('view', row)"
|
||||
>查看</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="row.approvalStatus === 'draft' && hasPermission('bill_payment_delete')"
|
||||
type="danger"
|
||||
@click="$emit('delete', row)"
|
||||
>删除</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
['draft', 'returned'].includes(row.approvalStatus) &&
|
||||
hasPermission('bill_payment_edit')
|
||||
"
|
||||
type="primary"
|
||||
@click="$emit('edit', row)"
|
||||
>编辑</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="row.approvalStatus !== 'draft'"
|
||||
type="primary"
|
||||
@click="$emit('flow', row)"
|
||||
>流程</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="row.approvalStatus === 'approved' && hasPermission('bill_payment_void')"
|
||||
type="danger"
|
||||
@click="$emit('void', row)"
|
||||
>作废</el-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="bill-payment-table__pagination">
|
||||
<el-pagination
|
||||
:current-page="page.current"
|
||||
:page-size="page.size"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="$emit('page-change', $event)"
|
||||
@size-change="$emit('size-change', $event)"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { billPaymentTableColumns } from '@/option/payment/billPayment';
|
||||
|
||||
export default {
|
||||
name: 'BillPaymentTable',
|
||||
props: {
|
||||
rows: { type: Array, default: () => [] },
|
||||
loading: { type: Boolean, default: false },
|
||||
page: { type: Object, required: true },
|
||||
},
|
||||
emits: ['add', 'view', 'edit', 'delete', 'flow', 'void', 'page-change', 'size-change'],
|
||||
data() {
|
||||
return { columns: billPaymentTableColumns };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
return String(this.userInfo?.authority || '').includes('admin');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.permission?.[code] === true;
|
||||
},
|
||||
statusType(status) {
|
||||
return (
|
||||
{ reviewing: 'warning', returned: 'danger', approved: 'success', voided: 'info' }[status] ||
|
||||
''
|
||||
);
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : value;
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.bill-payment-table {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.bill-payment-table__toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.bill-payment-table__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.bill-payment-table__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.bill-payment-table :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.bill-payment-table :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,474 @@
|
||||
<template>
|
||||
<basic-container class="invoice-page">
|
||||
<section class="invoice-page__search">
|
||||
<el-form :model="query" label-position="right" label-width="160px" @submit.prevent>
|
||||
<div class="invoice-page__search-grid">
|
||||
<el-form-item label="单据号">
|
||||
<el-input v-model="query.applicationNo" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属项目">
|
||||
<el-input v-model="query.projectName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组织">
|
||||
<el-input v-model="query.deptName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="金蝶单据状态">
|
||||
<el-select v-model="query.kingdeeStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in kingdeeStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="searchExpanded" label="状态">
|
||||
<el-select v-model="query.approvalStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in approvalStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="invoice-page__search-actions">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
<el-tooltip :content="searchExpanded ? '折叠' : '展开'" placement="top">
|
||||
<el-button text @click="searchExpanded = !searchExpanded">
|
||||
<el-icon><component :is="searchExpanded ? ArrowUp : ArrowDown" /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form>
|
||||
</section>
|
||||
|
||||
<section class="invoice-page__table-panel">
|
||||
<div class="invoice-page__toolbar">
|
||||
<div class="invoice-page__table-title">开票申请单列表</div>
|
||||
<div class="invoice-page__toolbar-actions">
|
||||
<el-button
|
||||
v-if="hasPermission('invoice_application_sync')"
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleBatchSync"
|
||||
>批量同步</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('invoice_application_add')"
|
||||
type="primary"
|
||||
@click="openCreate"
|
||||
>新增</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('invoice_application_export')"
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleExport"
|
||||
>导出</el-button
|
||||
>
|
||||
<el-tooltip content="刷新" placement="top">
|
||||
<el-button text :icon="Refresh" @click="loadTable" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="rows" border @selection-change="selection = $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 columns"
|
||||
:key="column.prop"
|
||||
v-bind="column"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="column.link && row[column.prop]" type="primary" @click="openView(row)">
|
||||
{{ row[column.prop] }}
|
||||
</el-link>
|
||||
<el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)">
|
||||
{{ displayValue(row[column.prop]) }}
|
||||
</el-tag>
|
||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="320" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="invoice-page__links">
|
||||
<el-link
|
||||
v-if="hasPermission('invoice_application_view')"
|
||||
type="primary"
|
||||
@click="openView(row)"
|
||||
>查看</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('invoice_application_edit') &&
|
||||
['draft', 'returned'].includes(row.approvalStatus)
|
||||
"
|
||||
type="primary"
|
||||
@click="openEdit(row)"
|
||||
>编辑</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="hasPermission('invoice_application_delete') && row.approvalStatus === 'draft'"
|
||||
type="danger"
|
||||
@click="handleDelete(row)"
|
||||
>删除</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('invoice_application_submit') &&
|
||||
['draft', 'returned'].includes(row.approvalStatus)
|
||||
"
|
||||
type="primary"
|
||||
@click="handleSubmit(row)"
|
||||
>提交</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="hasPermission('invoice_application_view') && row.approvalStatus !== 'draft'"
|
||||
type="primary"
|
||||
@click="openFlow(row)"
|
||||
>流程</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('invoice_application_approve') && row.approvalStatus === 'reviewing'
|
||||
"
|
||||
type="primary"
|
||||
@click="handleApprove(row)"
|
||||
>通过</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('invoice_application_approve') && row.approvalStatus === 'reviewing'
|
||||
"
|
||||
type="danger"
|
||||
@click="handleReturn(row)"
|
||||
>驳回</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('invoice_application_void') && row.approvalStatus === 'approved'
|
||||
"
|
||||
type="danger"
|
||||
@click="handleVoid(row)"
|
||||
>作废</el-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="invoice-page__pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="page.current"
|
||||
v-model:page-size="page.size"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadTable"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog v-model="flowDialog.visible" title="审批流程" width="900px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="开票申请单号">
|
||||
{{ flowDialog.row.applicationNo || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
{{ flowDialog.row.approvalStatusName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前节点">
|
||||
{{ flowDialog.row.currentNode || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前处理人">
|
||||
{{ flowDialog.row.currentProcessor || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-table
|
||||
v-loading="flowDialog.loading"
|
||||
:data="flowDialog.records"
|
||||
border
|
||||
class="invoice-page__flow-table"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column prop="actionName" label="操作" min-width="110" align="center" />
|
||||
<el-table-column label="原状态" min-width="100" align="center">
|
||||
<template #default="{ row }">{{ statusName(row.fromStatus) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="新状态" min-width="100" align="center">
|
||||
<template #default="{ row }">{{ statusName(row.toStatus) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operatorName" label="操作人" min-width="110" align="center" />
|
||||
<el-table-column prop="createTime" label="操作时间" min-width="170" align="center" />
|
||||
<el-table-column prop="reason" label="原因" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="150" />
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ArrowDown, ArrowUp, Refresh } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as XLSX from 'xlsx';
|
||||
import * as api from '@/api/payment/invoiceApplication';
|
||||
import { invoiceApplicationTableColumns } from '@/option/payment/invoiceApplication';
|
||||
|
||||
const emptyQuery = () => ({
|
||||
applicationNo: '',
|
||||
projectName: '',
|
||||
deptName: '',
|
||||
kingdeeStatus: '',
|
||||
approvalStatus: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'InvoiceApplication',
|
||||
data() {
|
||||
return {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
Refresh,
|
||||
query: emptyQuery(),
|
||||
searchExpanded: false,
|
||||
approvalStatusOptions: api.approvalStatusOptions,
|
||||
kingdeeStatusOptions: api.kingdeeStatusOptions,
|
||||
columns: invoiceApplicationTableColumns,
|
||||
rows: [],
|
||||
selection: [],
|
||||
loading: false,
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
flowDialog: { visible: false, loading: false, row: {}, records: [] },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
},
|
||||
mounted() {
|
||||
this.loadTable();
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.permission?.[code] !== false;
|
||||
},
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
async loadTable() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const data = this.unwrapData(
|
||||
await api.getList(this.page.current, this.page.size, this.query)
|
||||
);
|
||||
this.rows = data.records || [];
|
||||
this.page.total = Number(data.total || 0);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
resetSearch() {
|
||||
this.query = emptyQuery();
|
||||
this.handleSearch();
|
||||
},
|
||||
handleSizeChange() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
openCreate() {
|
||||
this.$router.push({ path: '/payment/invoice-application/form', query: { mode: 'add' } });
|
||||
},
|
||||
openEdit(row) {
|
||||
this.$router.push({
|
||||
path: '/payment/invoice-application/form',
|
||||
query: { mode: 'edit', id: row.id },
|
||||
});
|
||||
},
|
||||
openView(row) {
|
||||
this.$router.push({
|
||||
path: '/payment/invoice-application/form',
|
||||
query: { mode: 'view', id: row.id },
|
||||
});
|
||||
},
|
||||
async openFlow(row) {
|
||||
this.flowDialog.row = { ...row };
|
||||
this.flowDialog.records = [];
|
||||
this.flowDialog.visible = true;
|
||||
this.flowDialog.loading = true;
|
||||
try {
|
||||
const detail = this.unwrapData(await api.getDetail(row.id));
|
||||
this.flowDialog.row = { ...row, ...detail };
|
||||
this.flowDialog.records = detail.records || [];
|
||||
} finally {
|
||||
this.flowDialog.loading = false;
|
||||
}
|
||||
},
|
||||
async handleDelete(row) {
|
||||
await this.$confirm('确认删除该开票申请?', '提示', { type: 'warning' });
|
||||
await api.remove(row.id);
|
||||
this.$message.success('删除成功');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleSubmit(row) {
|
||||
await api.submit({ id: row.id });
|
||||
this.$message.success('提交成功');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleApprove(row) {
|
||||
await this.$confirm('确认审批通过该开票申请?', '提示', { type: 'warning' });
|
||||
await api.approve({ id: row.id });
|
||||
this.$message.success('审批通过');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleReturn(row) {
|
||||
const { value } = await this.$prompt('请输入驳回原因', '审批驳回');
|
||||
await api.returnBill({ id: row.id, reason: value });
|
||||
this.$message.success('已驳回');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleVoid(row) {
|
||||
const { value } = await this.$prompt('请输入作废原因', '作废开票申请');
|
||||
await api.voidBill({ id: row.id, reason: value });
|
||||
this.$message.success('作废成功');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleBatchSync() {
|
||||
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
|
||||
if (!rows.length) {
|
||||
this.$message.warning('请选择至少一条审批通过的开票申请');
|
||||
return;
|
||||
}
|
||||
await Promise.all(rows.map(row => api.syncKingdee(row.id)));
|
||||
this.$message.success(`已同步${rows.length}条开票申请`);
|
||||
this.loadTable();
|
||||
},
|
||||
async handleExport() {
|
||||
const data = this.unwrapData(await api.getList(1, 100000, this.query));
|
||||
const exportRows = (data.records || []).map(item => ({
|
||||
单据号: item.applicationNo,
|
||||
结算单号: item.settlementNos,
|
||||
所属项目: item.projectName,
|
||||
所属组织: item.deptName,
|
||||
开票方: item.issuerName,
|
||||
受票方: item.receiverName,
|
||||
开票金额: Number(item.invoiceAmount || 0).toFixed(2),
|
||||
创建人: item.createUserName,
|
||||
创建时间: item.createTime,
|
||||
状态: item.approvalStatusName,
|
||||
当前节点: item.currentNode,
|
||||
当前处理人: item.currentProcessor,
|
||||
金蝶单据号: item.kingdeeBillNo,
|
||||
金蝶单据状态: item.kingdeeStatusName,
|
||||
}));
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(exportRows), '开票申请');
|
||||
XLSX.writeFile(workbook, `开票申请${this.$dayjs().format('YYYY-MM-DD HH-mm-ss')}.xlsx`);
|
||||
},
|
||||
statusType(status) {
|
||||
return (
|
||||
{ approved: 'success', reviewing: 'warning', returned: 'danger', voided: 'info' }[status] ||
|
||||
''
|
||||
);
|
||||
},
|
||||
statusName(status) {
|
||||
return (
|
||||
{
|
||||
draft: '草稿',
|
||||
reviewing: '审批中',
|
||||
approved: '审批通过',
|
||||
returned: '已驳回',
|
||||
voided: '已作废',
|
||||
}[status] ||
|
||||
status ||
|
||||
'-'
|
||||
);
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : value;
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.invoice-page__search {
|
||||
padding: 12px 12px 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.invoice-page__search-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px 24px;
|
||||
}
|
||||
.invoice-page__search :deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.invoice-page__search :deep(.el-input),
|
||||
.invoice-page__search :deep(.el-select) {
|
||||
width: 100%;
|
||||
}
|
||||
.invoice-page__search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.invoice-page__table-panel {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.invoice-page__toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.invoice-page__toolbar-actions,
|
||||
.invoice-page__links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.invoice-page__table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.invoice-page__links {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
.invoice-page__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.invoice-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.invoice-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
.invoice-page__flow-table {
|
||||
margin-top: 16px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.invoice-page__search-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,834 @@
|
||||
<template>
|
||||
<basic-container class="receipt-form-page">
|
||||
<div class="receipt-form-page__title">
|
||||
{{ readonly ? '查看收票登记' : recordId ? '编辑收票登记' : '新增收票登记' }}
|
||||
</div>
|
||||
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="receipt-form-page__form"
|
||||
>
|
||||
<section class="receipt-form-page__section">
|
||||
<div class="receipt-form-page__section-title">收票基本信息</div>
|
||||
<el-row :gutter="32">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发票号码" prop="invoiceNo">
|
||||
<el-input v-model="form.invoiceNo" readonly placeholder="请选择金蝶票据池发票">
|
||||
<template v-if="!readonly" #append>
|
||||
<el-tooltip content="查询金蝶票据池" placement="top">
|
||||
<el-button :icon="Search" @click="openInvoiceDialog" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开票日期">
|
||||
<el-input v-model="form.invoiceDate" disabled placeholder="从金蝶票据池带出" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="发票类型">
|
||||
<el-input v-model="form.invoiceType" disabled placeholder="从金蝶票据池带出" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="税率">
|
||||
<el-input
|
||||
:model-value="formatRate(form.taxRate)"
|
||||
disabled
|
||||
placeholder="从金蝶票据池带出"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开票金额">
|
||||
<el-input
|
||||
:model-value="form.kingdeeInvoicePoolId ? formatMoney(form.invoiceAmount) : ''"
|
||||
disabled
|
||||
placeholder="从金蝶票据池带出"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="税额">
|
||||
<el-input
|
||||
:model-value="form.kingdeeInvoicePoolId ? formatMoney(form.taxAmount) : ''"
|
||||
disabled
|
||||
placeholder="从金蝶票据池带出"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="受票单位">
|
||||
<el-input v-model="form.receiverName" disabled placeholder="从金蝶票据池带出" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开票单位">
|
||||
<el-input v-model="form.issuerName" disabled placeholder="从金蝶票据池带出" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="关联结算单" prop="settlementIds">
|
||||
<el-input :model-value="settlementLabel" readonly placeholder="请选择应付正式结算单">
|
||||
<template v-if="!readonly" #append>
|
||||
<el-tooltip content="选择正式结算单" placement="top">
|
||||
<el-button :icon="Search" @click="openSettlementDialog" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户行">
|
||||
<el-input v-model="form.bankName" disabled placeholder="从金蝶票据池带出" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户账号">
|
||||
<el-input v-model="form.bankAccount" disabled placeholder="从金蝶票据池带出" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开票行">
|
||||
<el-input v-model="form.issuingBank" disabled placeholder="从金蝶票据池带出" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="电话">
|
||||
<el-input v-model="form.phone" :disabled="readonly" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户邮箱" prop="customerEmails">
|
||||
<el-select
|
||||
v-model="form.customerEmails"
|
||||
:disabled="readonly"
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
:multiple-limit="3"
|
||||
>
|
||||
<el-option
|
||||
v-for="email in customerEmailOptions"
|
||||
:key="email"
|
||||
:label="email"
|
||||
:value="email"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="部门邮箱" prop="departmentEmails">
|
||||
<el-select
|
||||
v-model="form.departmentEmails"
|
||||
:disabled="readonly"
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
:multiple-limit="3"
|
||||
>
|
||||
<el-option
|
||||
v-for="email in departmentEmailOptions"
|
||||
:key="email"
|
||||
:label="email"
|
||||
:value="email"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
|
||||
<section class="receipt-form-page__section">
|
||||
<div class="receipt-form-page__section-title">结算信息</div>
|
||||
<el-table :data="form.settlements" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="结算单号" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="openSettlementDetail(row)">
|
||||
{{ row.formalSettlementNo }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算总金额" min-width="150" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.settlementAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已收票金额" min-width="150" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.receivedInvoiceAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分摊发票金额" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.allocatedInvoiceAmount"
|
||||
:disabled="readonly"
|
||||
:min="0"
|
||||
:max="remainingAmount(row)"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="receipt-form-page__section">
|
||||
<div class="receipt-form-page__section-heading">
|
||||
<div class="receipt-form-page__section-title">附件信息</div>
|
||||
<el-button
|
||||
v-if="form.attachments.length"
|
||||
type="primary"
|
||||
plain
|
||||
:icon="Download"
|
||||
@click="downloadAttachments"
|
||||
>批量下载</el-button
|
||||
>
|
||||
</div>
|
||||
<el-table :data="form.attachments" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="文件名" min-width="220">
|
||||
<template #default="{ row }">{{ row.originalName || row.name || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="附件描述" min-width="240">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.description" :disabled="readonly" maxlength="200" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="size" label="文件大小" width="120" />
|
||||
<el-table-column prop="uploadUserName" label="上传人" width="140" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="170" />
|
||||
<el-table-column label="操作" width="120" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<div class="receipt-form-page__links">
|
||||
<el-link type="primary" :href="row.url || row.link" target="_blank">查看</el-link>
|
||||
<el-link v-if="!readonly" type="danger" @click="form.attachments.splice($index, 1)"
|
||||
>删除</el-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<vehicle-attachment-upload
|
||||
v-model="form.attachments"
|
||||
:readonly="readonly"
|
||||
:multiple="true"
|
||||
:limit="20"
|
||||
:max-size="500"
|
||||
:file-types="attachmentFileTypes"
|
||||
:show-file-list="false"
|
||||
button-text="上传附件"
|
||||
class="receipt-form-page__uploader"
|
||||
@change="normalizeAttachments"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="receipt-form-page__section">
|
||||
<div class="receipt-form-page__section-title">备注</div>
|
||||
<el-form-item class="receipt-form-page__remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
:disabled="readonly"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<div class="receipt-form-page__actions">
|
||||
<el-button v-if="!readonly" type="primary" plain @click="syncReferenceData">同步</el-button>
|
||||
<el-button v-if="!readonly && canSave" @click="saveDraft">保存</el-button>
|
||||
<el-button
|
||||
v-if="!readonly && canSave && hasPermission('invoice_receipt_submit')"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>提交</el-button
|
||||
>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<el-dialog v-model="invoiceDialog.visible" title="查询金蝶票据池" width="86%" append-to-body>
|
||||
<div class="receipt-form-page__dialog-search">
|
||||
<el-input
|
||||
v-model="invoiceDialog.keyword"
|
||||
clearable
|
||||
placeholder="发票号码、开票单位或受票单位"
|
||||
@keyup.enter="loadInvoicePool"
|
||||
/>
|
||||
<el-button type="primary" @click="loadInvoicePool">查询</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="invoiceDialog.loading"
|
||||
:data="invoiceDialog.rows"
|
||||
border
|
||||
highlight-current-row
|
||||
@current-change="invoiceDialog.current = $event"
|
||||
@row-dblclick="confirmInvoice"
|
||||
>
|
||||
<el-table-column prop="invoiceNo" label="发票号码" min-width="180" />
|
||||
<el-table-column prop="invoiceDate" label="开票日期" min-width="120" />
|
||||
<el-table-column prop="invoiceType" label="发票类型" min-width="130" />
|
||||
<el-table-column prop="issuerName" label="开票单位" min-width="170" />
|
||||
<el-table-column prop="receiverName" label="受票单位" min-width="170" />
|
||||
<el-table-column label="开票金额" min-width="130" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.invoiceAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="税率" min-width="100" align="center">
|
||||
<template #default="{ row }">{{ formatRate(row.taxRate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="税额" min-width="120" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="150" />
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="invoiceDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmInvoice()">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="settlementDialog.visible"
|
||||
title="选择应付正式结算单"
|
||||
width="86%"
|
||||
append-to-body
|
||||
>
|
||||
<div class="receipt-form-page__dialog-search">
|
||||
<el-input
|
||||
v-model="settlementDialog.keyword"
|
||||
clearable
|
||||
placeholder="结算单号、项目或合同"
|
||||
@keyup.enter="loadSettlementCandidates"
|
||||
/>
|
||||
<el-button type="primary" @click="loadSettlementCandidates">查询</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="settlementTable"
|
||||
v-loading="settlementDialog.loading"
|
||||
:data="settlementDialog.rows"
|
||||
border
|
||||
@selection-change="settlementDialog.selection = $event"
|
||||
>
|
||||
<el-table-column type="selection" width="52" align="center" />
|
||||
<el-table-column prop="formalSettlementNo" label="结算单号" min-width="170" />
|
||||
<el-table-column prop="projectName" label="所属项目" min-width="150" />
|
||||
<el-table-column prop="deptName" label="所属组织" min-width="150" />
|
||||
<el-table-column prop="contractNo" label="合同编号" min-width="150" />
|
||||
<el-table-column prop="payerName" label="付款方" min-width="150" />
|
||||
<el-table-column prop="payeeName" label="收款方" min-width="150" />
|
||||
<el-table-column label="结算总金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.settlementAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已收票金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.receivedInvoiceAmount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmSettlements">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Download, Search } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/invoiceReceipt';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
kingdeeInvoicePoolId: null,
|
||||
invoiceNo: '',
|
||||
invoiceDate: '',
|
||||
invoiceType: '',
|
||||
taxRate: null,
|
||||
invoiceAmount: null,
|
||||
taxAmount: null,
|
||||
receiverName: '',
|
||||
issuerName: '',
|
||||
bankName: '',
|
||||
bankAccount: '',
|
||||
issuingBank: '',
|
||||
phone: '',
|
||||
customerEmails: [],
|
||||
departmentEmails: [],
|
||||
projectName: '',
|
||||
deptName: '',
|
||||
payerName: '',
|
||||
payeeName: '',
|
||||
settlements: [],
|
||||
attachments: [],
|
||||
remark: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'InvoiceReceiptForm',
|
||||
data() {
|
||||
return {
|
||||
Download,
|
||||
Search,
|
||||
form: emptyForm(),
|
||||
customerEmailOptions: [],
|
||||
departmentEmailOptions: [],
|
||||
attachmentFileTypes: [
|
||||
'pdf',
|
||||
'bmp',
|
||||
'jpeg',
|
||||
'png',
|
||||
'jpg',
|
||||
'doc',
|
||||
'docx',
|
||||
'ppt',
|
||||
'pptx',
|
||||
'xlsx',
|
||||
'xls',
|
||||
'eml',
|
||||
'msg',
|
||||
'zip',
|
||||
],
|
||||
invoiceDialog: { visible: false, loading: false, keyword: '', rows: [], current: null },
|
||||
settlementDialog: {
|
||||
visible: false,
|
||||
loading: false,
|
||||
keyword: '',
|
||||
rows: [],
|
||||
selection: [],
|
||||
},
|
||||
rules: {
|
||||
invoiceNo: [{ required: true, validator: this.validateInvoice, trigger: 'change' }],
|
||||
settlementIds: [{ required: true, validator: this.validateSettlements, trigger: 'change' }],
|
||||
customerEmails: [{ validator: this.validateEmails, trigger: 'change' }],
|
||||
departmentEmails: [{ validator: this.validateEmails, trigger: 'change' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
recordId() {
|
||||
return this.$route.query.id || '';
|
||||
},
|
||||
readonly() {
|
||||
return this.$route.query.mode === 'view';
|
||||
},
|
||||
canSave() {
|
||||
const code = this.recordId ? 'invoice_receipt_edit' : 'invoice_receipt_add';
|
||||
return this.hasPermission(code);
|
||||
},
|
||||
settlementLabel() {
|
||||
return this.form.settlements
|
||||
.map(item => item.formalSettlementNo)
|
||||
.filter(Boolean)
|
||||
.join(',');
|
||||
},
|
||||
settlementIds() {
|
||||
return this.form.settlements.map(item => item.formalSettlementId || item.settlementId);
|
||||
},
|
||||
allocatedTotal() {
|
||||
return this.form.settlements.reduce(
|
||||
(total, item) => total + Number(item.allocatedInvoiceAmount || 0),
|
||||
0
|
||||
);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initialize();
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.permission?.[code] !== false;
|
||||
},
|
||||
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 [];
|
||||
}
|
||||
},
|
||||
parseEmails(value) {
|
||||
return String(value || '')
|
||||
.split(/[;,,;]/)
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean);
|
||||
},
|
||||
async initialize() {
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
const userEmail = userInfo.email || '';
|
||||
this.departmentEmailOptions = userEmail ? [userEmail] : [];
|
||||
if (!this.recordId) {
|
||||
if (userEmail) this.form.departmentEmails = [userEmail];
|
||||
return;
|
||||
}
|
||||
const data = this.unwrapData(await api.getDetail(this.recordId));
|
||||
this.form = {
|
||||
...emptyForm(),
|
||||
...data,
|
||||
customerEmails: this.parseEmails(data.customerEmails),
|
||||
departmentEmails: this.parseEmails(data.departmentEmails),
|
||||
settlements: (data.settlements || []).map(item => ({
|
||||
...item,
|
||||
settlementId: item.formalSettlementId,
|
||||
})),
|
||||
attachments: this.parse(data.attachmentsJson),
|
||||
};
|
||||
if (this.settlementIds.length) await this.loadReferenceInformation();
|
||||
},
|
||||
validateInvoice(rule, value, callback) {
|
||||
if (!value || !this.form.kingdeeInvoicePoolId) {
|
||||
callback(new Error('请选择金蝶票据池发票'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateSettlements(rule, value, callback) {
|
||||
if (!this.form.settlements.length) {
|
||||
callback(new Error('请选择应付正式结算单'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateEmails(rule, value, callback) {
|
||||
const emails = Array.isArray(value) ? value : this.parseEmails(value);
|
||||
const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (emails.length > 3) {
|
||||
callback(new Error('最多填写3个邮箱'));
|
||||
return;
|
||||
}
|
||||
if (emails.some(email => !valid.test(email))) {
|
||||
callback(new Error('请输入正确的邮箱'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
async openInvoiceDialog() {
|
||||
this.invoiceDialog.visible = true;
|
||||
this.invoiceDialog.keyword = this.form.invoiceNo || '';
|
||||
await this.loadInvoicePool();
|
||||
},
|
||||
async loadInvoicePool() {
|
||||
this.invoiceDialog.loading = true;
|
||||
try {
|
||||
this.invoiceDialog.rows =
|
||||
this.unwrapData(await api.getInvoicePool(this.invoiceDialog.keyword)) || [];
|
||||
} finally {
|
||||
this.invoiceDialog.loading = false;
|
||||
}
|
||||
},
|
||||
confirmInvoice(row, options = {}) {
|
||||
const selected = row?.id ? row : this.invoiceDialog.current;
|
||||
if (!selected) {
|
||||
this.$message.warning('请选择一张金蝶票据池发票');
|
||||
return;
|
||||
}
|
||||
if (this.form.settlements.length) {
|
||||
const first = this.form.settlements[0];
|
||||
if (selected.receiverName !== first.payerName || selected.issuerName !== first.payeeName) {
|
||||
this.$message.warning('金蝶发票的开票单位、受票单位与结算单收付款方不一致');
|
||||
return;
|
||||
}
|
||||
}
|
||||
const preserveUserInput = options.preserveUserInput === true;
|
||||
Object.assign(this.form, {
|
||||
kingdeeInvoicePoolId: selected.id,
|
||||
invoiceNo: selected.invoiceNo,
|
||||
invoiceDate: selected.invoiceDate,
|
||||
invoiceType: selected.invoiceType,
|
||||
taxRate: selected.taxRate,
|
||||
invoiceAmount: selected.invoiceAmount,
|
||||
taxAmount: selected.taxAmount,
|
||||
receiverName: selected.receiverName,
|
||||
issuerName: selected.issuerName,
|
||||
bankName: selected.bankName,
|
||||
bankAccount: selected.bankAccount,
|
||||
issuingBank: selected.issuingBank,
|
||||
phone: preserveUserInput ? this.form.phone : selected.phone || '',
|
||||
customerEmails: preserveUserInput
|
||||
? this.form.customerEmails
|
||||
: this.parseEmails(selected.customerEmails),
|
||||
departmentEmails: preserveUserInput
|
||||
? this.form.departmentEmails
|
||||
: this.parseEmails(selected.departmentEmails),
|
||||
attachments: preserveUserInput
|
||||
? this.form.attachments
|
||||
: this.parse(selected.attachmentsJson),
|
||||
});
|
||||
this.customerEmailOptions = [...this.form.customerEmails];
|
||||
this.departmentEmailOptions = [
|
||||
...new Set([...this.departmentEmailOptions, ...this.form.departmentEmails]),
|
||||
];
|
||||
this.invoiceDialog.visible = false;
|
||||
this.$refs.formRef?.validateField('invoiceNo').catch(() => {});
|
||||
return true;
|
||||
},
|
||||
async openSettlementDialog() {
|
||||
this.settlementDialog.visible = true;
|
||||
await this.loadSettlementCandidates();
|
||||
},
|
||||
async loadSettlementCandidates() {
|
||||
this.settlementDialog.loading = true;
|
||||
try {
|
||||
this.settlementDialog.rows =
|
||||
this.unwrapData(
|
||||
await api.getSettlementCandidates(this.settlementDialog.keyword, this.form.id)
|
||||
) || [];
|
||||
} finally {
|
||||
this.settlementDialog.loading = false;
|
||||
}
|
||||
},
|
||||
async confirmSettlements() {
|
||||
const rows = this.settlementDialog.selection;
|
||||
if (!rows.length) {
|
||||
this.$message.warning('请至少选择一张应付正式结算单');
|
||||
return;
|
||||
}
|
||||
const first = rows[0];
|
||||
const incompatible = rows.some(
|
||||
item =>
|
||||
String(item.projectId) !== String(first.projectId) ||
|
||||
String(item.deptId) !== String(first.deptId) ||
|
||||
item.payerName !== first.payerName ||
|
||||
item.payeeName !== first.payeeName
|
||||
);
|
||||
if (incompatible) {
|
||||
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.form.kingdeeInvoicePoolId &&
|
||||
(this.form.receiverName !== first.payerName || this.form.issuerName !== first.payeeName)
|
||||
) {
|
||||
this.$message.warning('金蝶发票的开票单位、受票单位与结算单收付款方不一致');
|
||||
return;
|
||||
}
|
||||
this.form.settlements = rows.map(item => ({
|
||||
...item,
|
||||
formalSettlementId: item.id,
|
||||
settlementId: item.id,
|
||||
allocatedInvoiceAmount: 0,
|
||||
}));
|
||||
this.form.projectName = first.projectName;
|
||||
this.form.deptName = first.deptName;
|
||||
this.form.payerName = first.payerName;
|
||||
this.form.payeeName = first.payeeName;
|
||||
this.settlementDialog.visible = false;
|
||||
await this.loadReferenceInformation();
|
||||
this.$refs.formRef?.validateField('settlementIds').catch(() => {});
|
||||
},
|
||||
async loadReferenceInformation() {
|
||||
const data = this.unwrapData(await api.getReferenceInformation(this.settlementIds.join(',')));
|
||||
this.customerEmailOptions = data.customerEmails || [];
|
||||
this.departmentEmailOptions = [
|
||||
...new Set([...this.departmentEmailOptions, ...(data.departmentEmails || [])]),
|
||||
];
|
||||
if (!this.form.customerEmails.length && this.customerEmailOptions.length) {
|
||||
this.form.customerEmails = [this.customerEmailOptions[0]];
|
||||
}
|
||||
},
|
||||
remainingAmount(row) {
|
||||
return Math.max(
|
||||
Number(row.settlementAmount || 0) - Number(row.receivedInvoiceAmount || 0),
|
||||
0
|
||||
);
|
||||
},
|
||||
normalizeAttachments(files) {
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
const userName = userInfo.realName || 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,
|
||||
}));
|
||||
},
|
||||
downloadAttachments() {
|
||||
this.form.attachments.forEach(file => {
|
||||
const url = file.url || file.link;
|
||||
if (!url) return;
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = file.originalName || file.name || '';
|
||||
anchor.target = '_blank';
|
||||
anchor.click();
|
||||
});
|
||||
},
|
||||
openSettlementDetail(row) {
|
||||
const id = row.formalSettlementId || row.settlementId || row.id;
|
||||
if (!id) return;
|
||||
this.$router.push({
|
||||
path: '/settlement/formal-settlement/form',
|
||||
query: { mode: 'view', id, name: '查看正式结算' },
|
||||
});
|
||||
},
|
||||
validateBusiness() {
|
||||
const invoiceAmount = Number(this.form.invoiceAmount || 0);
|
||||
if (invoiceAmount <= 0) throw new Error('开票金额必须大于0');
|
||||
if (Math.abs(this.allocatedTotal - invoiceAmount) > 0.001) {
|
||||
throw new Error('分摊发票金额总和必须等于开票金额');
|
||||
}
|
||||
for (const row of this.form.settlements) {
|
||||
const allocated = Number(row.allocatedInvoiceAmount);
|
||||
if (!Number.isFinite(allocated) || allocated < 0) {
|
||||
throw new Error('分摊发票金额必须大于或等于0');
|
||||
}
|
||||
if (allocated - this.remainingAmount(row) > 0.001) {
|
||||
throw new Error(`结算单${row.formalSettlementNo}的累计收票金额不能超过结算总金额`);
|
||||
}
|
||||
}
|
||||
},
|
||||
payload() {
|
||||
return {
|
||||
id: this.form.id,
|
||||
kingdeeInvoicePoolId: this.form.kingdeeInvoicePoolId,
|
||||
phone: this.form.phone,
|
||||
customerEmails: this.form.customerEmails.join(';'),
|
||||
departmentEmails: this.form.departmentEmails.join(';'),
|
||||
attachmentsJson: JSON.stringify(this.form.attachments || []),
|
||||
remark: this.form.remark,
|
||||
settlements: this.form.settlements.map(item => ({
|
||||
settlementId: item.formalSettlementId || item.settlementId,
|
||||
allocatedInvoiceAmount: item.allocatedInvoiceAmount,
|
||||
})),
|
||||
};
|
||||
},
|
||||
async saveDraft() {
|
||||
await this.$refs.formRef.validate();
|
||||
try {
|
||||
this.validateBusiness();
|
||||
} catch (error) {
|
||||
this.$message.warning(error.message);
|
||||
return false;
|
||||
}
|
||||
const id = this.unwrapData(await api.save(this.payload()));
|
||||
this.form.id = id;
|
||||
this.$message.success('保存成功');
|
||||
return true;
|
||||
},
|
||||
async submitForm() {
|
||||
const saved = await this.saveDraft();
|
||||
if (!saved) return;
|
||||
await api.submit({ id: this.form.id });
|
||||
this.$message.success('提交成功');
|
||||
this.goBack();
|
||||
},
|
||||
async syncReferenceData() {
|
||||
if (!this.form.invoiceNo) {
|
||||
this.$message.warning('请先选择金蝶票据池发票');
|
||||
return;
|
||||
}
|
||||
const rows = this.unwrapData(await api.getInvoicePool(this.form.invoiceNo)) || [];
|
||||
const invoice = rows.find(item => item.invoiceNo === this.form.invoiceNo);
|
||||
if (!invoice) {
|
||||
this.$message.warning('金蝶票据池中未查询到该发票');
|
||||
return;
|
||||
}
|
||||
if (!this.confirmInvoice(invoice, { preserveUserInput: true })) return;
|
||||
if (this.settlementIds.length) await this.loadReferenceInformation();
|
||||
this.$message.success('同步成功');
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/payment/invoice-receipt');
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
formatRate(value) {
|
||||
return value === null || value === undefined || value === '' ? '' : `${Number(value)}%`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.receipt-form-page__title,
|
||||
.receipt-form-page__section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.receipt-form-page__title::before,
|
||||
.receipt-form-page__section-title::before {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
.receipt-form-page__section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.receipt-form-page__section-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.receipt-form-page__section-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.receipt-form-page__section-heading .receipt-form-page__section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.receipt-form-page__form :deep(.el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.receipt-form-page__form :deep(.el-select),
|
||||
.receipt-form-page__form :deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
.receipt-form-page__links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.receipt-form-page__remark :deep(.el-form-item__content) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.receipt-form-page__uploader {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.receipt-form-page__actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 16px 0 8px;
|
||||
}
|
||||
.receipt-form-page__dialog-search {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.receipt-form-page__dialog-search .el-input {
|
||||
width: 360px;
|
||||
}
|
||||
.receipt-form-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.receipt-form-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.receipt-form-page__form :deep(.el-col-12) {
|
||||
max-width: 100%;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,480 @@
|
||||
<template>
|
||||
<basic-container class="receipt-page">
|
||||
<section class="receipt-page__search">
|
||||
<el-form :model="query" label-position="right" label-width="160px" @submit.prevent>
|
||||
<div class="receipt-page__search-grid">
|
||||
<el-form-item label="发票号码">
|
||||
<el-input v-model="query.invoiceNo" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开票日期">
|
||||
<el-date-picker
|
||||
v-model="query.invoiceDate"
|
||||
type="date"
|
||||
clearable
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属项目">
|
||||
<el-input v-model="query.projectName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组织">
|
||||
<el-input v-model="query.deptName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="searchExpanded" label="审核状态">
|
||||
<el-select v-model="query.approvalStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in approvalStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="searchExpanded" label="金蝶单据状态">
|
||||
<el-select v-model="query.kingdeeStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in kingdeeStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="receipt-page__search-actions">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
<el-tooltip :content="searchExpanded ? '折叠' : '展开'" placement="top">
|
||||
<el-button text @click="searchExpanded = !searchExpanded">
|
||||
<el-icon><component :is="searchExpanded ? ArrowUp : ArrowDown" /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form>
|
||||
</section>
|
||||
|
||||
<section class="receipt-page__table-panel">
|
||||
<div class="receipt-page__toolbar">
|
||||
<div class="receipt-page__table-title">收票单列表</div>
|
||||
<div class="receipt-page__toolbar-actions">
|
||||
<el-button
|
||||
v-if="hasPermission('invoice_receipt_sync')"
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleBatchSync"
|
||||
>批量同步</el-button
|
||||
>
|
||||
<el-button v-if="hasPermission('invoice_receipt_add')" type="primary" @click="openCreate"
|
||||
>新增</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('invoice_receipt_export')"
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleExport"
|
||||
>导出</el-button
|
||||
>
|
||||
<el-tooltip content="刷新" placement="top">
|
||||
<el-button text :icon="Refresh" @click="loadTable" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="rows" border @selection-change="selection = $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 columns"
|
||||
:key="column.prop"
|
||||
v-bind="column"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="column.link && row[column.prop]" type="primary" @click="openView(row)">
|
||||
{{ row[column.prop] }}
|
||||
</el-link>
|
||||
<el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)">
|
||||
{{ displayValue(row[column.prop]) }}
|
||||
</el-tag>
|
||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="320" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="receipt-page__links">
|
||||
<el-link
|
||||
v-if="hasPermission('invoice_receipt_view')"
|
||||
type="primary"
|
||||
@click="openView(row)"
|
||||
>查看</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('invoice_receipt_edit') &&
|
||||
['draft', 'returned'].includes(row.approvalStatus)
|
||||
"
|
||||
type="primary"
|
||||
@click="openEdit(row)"
|
||||
>编辑</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="hasPermission('invoice_receipt_delete') && row.approvalStatus === 'draft'"
|
||||
type="danger"
|
||||
@click="handleDelete(row)"
|
||||
>删除</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('invoice_receipt_submit') &&
|
||||
['draft', 'returned'].includes(row.approvalStatus)
|
||||
"
|
||||
type="primary"
|
||||
@click="handleSubmit(row)"
|
||||
>提交</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="hasPermission('invoice_receipt_view') && row.approvalStatus !== 'draft'"
|
||||
type="primary"
|
||||
@click="openFlow(row)"
|
||||
>流程</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('invoice_receipt_approve') && row.approvalStatus === 'reviewing'
|
||||
"
|
||||
type="primary"
|
||||
@click="handleApprove(row)"
|
||||
>通过</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('invoice_receipt_approve') && row.approvalStatus === 'reviewing'
|
||||
"
|
||||
type="danger"
|
||||
@click="handleReturn(row)"
|
||||
>驳回</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="hasPermission('invoice_receipt_void') && row.approvalStatus === 'approved'"
|
||||
type="danger"
|
||||
@click="handleVoid(row)"
|
||||
>作废</el-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="receipt-page__pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="page.current"
|
||||
v-model:page-size="page.size"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadTable"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog v-model="flowDialog.visible" title="审批流程" width="900px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="发票号码">
|
||||
{{ flowDialog.row.invoiceNo || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审核状态">
|
||||
{{ flowDialog.row.approvalStatusName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前节点">
|
||||
{{ flowDialog.row.currentNode || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前处理人">
|
||||
{{ flowDialog.row.currentProcessor || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-table
|
||||
v-loading="flowDialog.loading"
|
||||
:data="flowDialog.records"
|
||||
border
|
||||
class="receipt-page__flow-table"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column prop="actionName" label="操作" min-width="110" align="center" />
|
||||
<el-table-column label="原状态" min-width="100" align="center">
|
||||
<template #default="{ row }">{{ statusName(row.fromStatus) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="新状态" min-width="100" align="center">
|
||||
<template #default="{ row }">{{ statusName(row.toStatus) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operatorName" label="操作人" min-width="110" align="center" />
|
||||
<el-table-column prop="createTime" label="操作时间" min-width="170" align="center" />
|
||||
<el-table-column prop="reason" label="原因" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="kingdeeBillNo" label="金蝶单据号" min-width="150" />
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ArrowDown, ArrowUp, Refresh } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as XLSX from 'xlsx';
|
||||
import * as api from '@/api/payment/invoiceReceipt';
|
||||
import { invoiceReceiptTableColumns } from '@/option/payment/invoiceReceipt';
|
||||
|
||||
const emptyQuery = () => ({
|
||||
invoiceNo: '',
|
||||
invoiceDate: '',
|
||||
projectName: '',
|
||||
deptName: '',
|
||||
approvalStatus: '',
|
||||
kingdeeStatus: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'InvoiceReceipt',
|
||||
data() {
|
||||
return {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
Refresh,
|
||||
query: emptyQuery(),
|
||||
searchExpanded: false,
|
||||
approvalStatusOptions: api.approvalStatusOptions,
|
||||
kingdeeStatusOptions: api.kingdeeStatusOptions,
|
||||
columns: invoiceReceiptTableColumns,
|
||||
rows: [],
|
||||
selection: [],
|
||||
loading: false,
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
flowDialog: { visible: false, loading: false, row: {}, records: [] },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
},
|
||||
mounted() {
|
||||
this.loadTable();
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.permission?.[code] !== false;
|
||||
},
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
async loadTable() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const data = this.unwrapData(
|
||||
await api.getList(this.page.current, this.page.size, this.query)
|
||||
);
|
||||
this.rows = data.records || [];
|
||||
this.page.total = Number(data.total || 0);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
resetSearch() {
|
||||
this.query = emptyQuery();
|
||||
this.handleSearch();
|
||||
},
|
||||
handleSizeChange() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
openCreate() {
|
||||
this.$router.push({ path: '/payment/invoice-receipt/form', query: { mode: 'add' } });
|
||||
},
|
||||
openEdit(row) {
|
||||
this.$router.push({
|
||||
path: '/payment/invoice-receipt/form',
|
||||
query: { mode: 'edit', id: row.id },
|
||||
});
|
||||
},
|
||||
openView(row) {
|
||||
this.$router.push({
|
||||
path: '/payment/invoice-receipt/form',
|
||||
query: { mode: 'view', id: row.id },
|
||||
});
|
||||
},
|
||||
async openFlow(row) {
|
||||
this.flowDialog.row = { ...row };
|
||||
this.flowDialog.records = [];
|
||||
this.flowDialog.visible = true;
|
||||
this.flowDialog.loading = true;
|
||||
try {
|
||||
const detail = this.unwrapData(await api.getDetail(row.id));
|
||||
this.flowDialog.row = { ...row, ...detail };
|
||||
this.flowDialog.records = detail.records || [];
|
||||
} finally {
|
||||
this.flowDialog.loading = false;
|
||||
}
|
||||
},
|
||||
async handleDelete(row) {
|
||||
await this.$confirm('确认删除该收票登记?', '提示', { type: 'warning' });
|
||||
await api.remove(row.id);
|
||||
this.$message.success('删除成功');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleSubmit(row) {
|
||||
await api.submit({ id: row.id });
|
||||
this.$message.success('提交成功');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleApprove(row) {
|
||||
await this.$confirm('确认审批通过该收票登记?', '提示', { type: 'warning' });
|
||||
await api.approve({ id: row.id });
|
||||
this.$message.success('审批通过');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleReturn(row) {
|
||||
const { value } = await this.$prompt('请输入驳回原因', '审批驳回');
|
||||
await api.returnBill({ id: row.id, reason: value });
|
||||
this.$message.success('已驳回');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleVoid(row) {
|
||||
const { value } = await this.$prompt('请输入作废原因', '作废收票登记');
|
||||
await api.voidBill({ id: row.id, reason: value });
|
||||
this.$message.success('作废成功');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleBatchSync() {
|
||||
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
|
||||
if (!rows.length) {
|
||||
this.$message.warning('请选择至少一条审批通过的收票登记');
|
||||
return;
|
||||
}
|
||||
await Promise.all(rows.map(row => api.syncKingdee(row.id)));
|
||||
this.$message.success(`已同步${rows.length}条收票登记`);
|
||||
this.loadTable();
|
||||
},
|
||||
async handleExport() {
|
||||
const data = this.unwrapData(await api.getList(1, 100000, this.query));
|
||||
const exportRows = (data.records || []).map(item => ({
|
||||
发票号: item.invoiceNo,
|
||||
开票日期: item.invoiceDate,
|
||||
所属项目: item.projectName,
|
||||
所属组织: item.deptName,
|
||||
付款方: item.payerName,
|
||||
收款方: item.payeeName,
|
||||
收票金额: Number(item.invoiceAmount || 0).toFixed(2),
|
||||
审核状态: item.approvalStatusName,
|
||||
当前节点: item.currentNode,
|
||||
当前处理人: item.currentProcessor,
|
||||
创建人: item.createUserName,
|
||||
创建时间: item.createTime,
|
||||
金蝶单据号: item.kingdeeBillNo,
|
||||
金蝶单据状态: item.kingdeeStatusName,
|
||||
}));
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(exportRows), '收票登记');
|
||||
XLSX.writeFile(workbook, `收票登记${this.$dayjs().format('YYYY-MM-DD HH-mm-ss')}.xlsx`);
|
||||
},
|
||||
statusType(status) {
|
||||
return (
|
||||
{ approved: 'success', reviewing: 'warning', returned: 'danger', voided: 'info' }[status] ||
|
||||
''
|
||||
);
|
||||
},
|
||||
statusName(status) {
|
||||
return (
|
||||
{
|
||||
draft: '草稿',
|
||||
reviewing: '审批中',
|
||||
approved: '审批通过',
|
||||
returned: '已驳回',
|
||||
voided: '已作废',
|
||||
}[status] ||
|
||||
status ||
|
||||
'-'
|
||||
);
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : value;
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.receipt-page__search {
|
||||
padding: 12px 12px 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.receipt-page__search-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px 24px;
|
||||
}
|
||||
.receipt-page__search :deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.receipt-page__search :deep(.el-input),
|
||||
.receipt-page__search :deep(.el-select),
|
||||
.receipt-page__search :deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
}
|
||||
.receipt-page__search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.receipt-page__table-panel {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.receipt-page__toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.receipt-page__table-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.receipt-page__toolbar-actions,
|
||||
.receipt-page__links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.receipt-page__links {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
.receipt-page__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.receipt-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.receipt-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
.receipt-page__flow-table {
|
||||
margin-top: 16px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.receipt-page__search-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,852 @@
|
||||
<template>
|
||||
<basic-container class="payment-form-page">
|
||||
<div class="payment-form-page__title">
|
||||
{{ readonly ? '查看付款申请' : recordId ? '编辑付款申请' : '新增付款申请' }}
|
||||
</div>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="payment-form-page__form"
|
||||
>
|
||||
<section class="payment-form-page__section">
|
||||
<div class="payment-form-page__section-title">付款基本信息</div>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12"
|
||||
><el-form-item label="单据号"
|
||||
><el-input
|
||||
v-model="form.paymentNo"
|
||||
disabled
|
||||
placeholder="系统自动生成" /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="付款类型" prop="paymentType"
|
||||
><el-select v-model="form.paymentType" :disabled="readonly" @change="handleTypeChange"
|
||||
><el-option
|
||||
v-for="item in paymentTypeOptions"
|
||||
:key="item.value"
|
||||
v-bind="item" /></el-select></el-form-item
|
||||
></el-col>
|
||||
<el-col v-if="form.paymentType !== 'project_advance'" :span="12"
|
||||
><el-form-item label="结算单号" prop="referenceId"
|
||||
><el-input
|
||||
v-model="referenceLabel"
|
||||
readonly
|
||||
:disabled="readonly"
|
||||
placeholder="请选择预结算/正式结算单"
|
||||
><template #append
|
||||
><el-button :disabled="readonly" @click="openReference">选择</el-button></template
|
||||
></el-input
|
||||
></el-form-item
|
||||
></el-col
|
||||
>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="所属项目"
|
||||
><el-input v-model="form.projectName" disabled
|
||||
><template v-if="form.paymentType === 'project_advance'" #append
|
||||
><el-button :disabled="readonly" @click="openReference">选择</el-button></template
|
||||
></el-input
|
||||
></el-form-item
|
||||
></el-col
|
||||
>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="合同名称"
|
||||
><el-input v-model="form.contractName" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="结算金额"
|
||||
><el-input v-model="form.settlementAmount" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="可付款金额"
|
||||
><el-input v-model="form.payableAmount" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="票款类型"
|
||||
><el-input v-model="form.billType" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="付款比例" prop="paymentRatio"
|
||||
><el-input-number
|
||||
v-model="form.paymentRatio"
|
||||
:disabled="readonly"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
style="width: 100%" /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="申请付款金额" prop="appliedAmount"
|
||||
><el-input-number
|
||||
v-model="form.appliedAmount"
|
||||
:disabled="readonly"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
style="width: 100%" /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="付款方式" prop="paymentMethod"
|
||||
><el-select
|
||||
v-model="form.paymentMethod"
|
||||
:disabled="readonly"
|
||||
@change="handlePaymentMethodChange"
|
||||
><el-option
|
||||
v-for="item in paymentMethodOptions"
|
||||
:key="item.value"
|
||||
v-bind="item" /></el-select></el-form-item
|
||||
></el-col>
|
||||
<el-col v-if="billPayment" :span="12"
|
||||
><el-form-item label="汇票票据" prop="billLedgerId"
|
||||
><el-select
|
||||
v-model="form.billLedgerId"
|
||||
:disabled="readonly"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:remote-method="loadBillOptions"
|
||||
:loading="billLoading"
|
||||
placeholder="请选择可用汇票"
|
||||
@change="handleBillChange"
|
||||
><el-option
|
||||
v-for="item in billOptions"
|
||||
:key="item.id"
|
||||
:label="`${item.billNo}|余额${formatMoney(item.availableBalance)}|${
|
||||
item.maturityDate
|
||||
}`"
|
||||
:value="item.id" /></el-select></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="收款方"
|
||||
><el-input v-model="form.payeeName" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="收款账号"
|
||||
><el-input v-model="form.bankAccount" :disabled="readonly" /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="开户银行"
|
||||
><el-input v-model="form.bankName" :disabled="readonly" /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="申请人"
|
||||
><el-input v-model="form.applicantName" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="申请日期"
|
||||
><el-input v-model="form.applyDate" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="24"
|
||||
><el-form-item label="备注"
|
||||
><el-input
|
||||
v-model="form.remark"
|
||||
:disabled="readonly"
|
||||
type="textarea"
|
||||
maxlength="200"
|
||||
show-word-limit /></el-form-item
|
||||
></el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
<section class="payment-form-page__section">
|
||||
<div class="payment-form-page__section-title">结算信息</div>
|
||||
<el-table :data="settlementRows" border
|
||||
><el-table-column type="index" label="序号" width="70" /><el-table-column
|
||||
prop="settlementNo"
|
||||
label="结算单号"
|
||||
min-width="150" /><el-table-column
|
||||
prop="settlementAmount"
|
||||
label="结算总金额"
|
||||
min-width="130" /><el-table-column
|
||||
prop="payableAmount"
|
||||
label="可付款金额"
|
||||
min-width="130" /><el-table-column
|
||||
prop="paymentRatio"
|
||||
label="付款比例"
|
||||
min-width="110" /><el-table-column
|
||||
prop="appliedAmount"
|
||||
label="付款金额"
|
||||
min-width="130"
|
||||
/></el-table>
|
||||
</section>
|
||||
<section class="payment-form-page__section">
|
||||
<div class="payment-form-page__section-title">发票信息</div>
|
||||
<div class="payment-form-page__section-actions">
|
||||
<el-button type="primary" plain :disabled="readonly" @click="addInvoice"
|
||||
>新增发票</el-button
|
||||
>
|
||||
</div>
|
||||
<el-table :data="form.invoices" border
|
||||
><el-table-column type="index" label="序号" width="70" /><el-table-column
|
||||
prop="settlementNo"
|
||||
label="结算单号"
|
||||
min-width="120"
|
||||
/><el-table-column label="发票号" min-width="140"
|
||||
><template #default="{ row }"
|
||||
><el-input v-model="row.invoiceNo" :disabled="readonly" /></template></el-table-column
|
||||
><el-table-column label="开票日期" min-width="150"
|
||||
><template #default="{ row }"
|
||||
><el-date-picker
|
||||
v-model="row.invoiceDate"
|
||||
value-format="YYYY-MM-DD"
|
||||
:disabled="readonly" /></template></el-table-column
|
||||
><el-table-column label="发票类型" min-width="130"
|
||||
><template #default="{ row }"
|
||||
><el-input
|
||||
v-model="row.invoiceType"
|
||||
:disabled="readonly" /></template></el-table-column
|
||||
><el-table-column label="税率" min-width="100"
|
||||
><template #default="{ row }"
|
||||
><el-input-number
|
||||
v-model="row.taxRate"
|
||||
:disabled="readonly"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:controls="false" /></template></el-table-column
|
||||
><el-table-column label="发票金额(含税)" min-width="150"
|
||||
><template #default="{ row }"
|
||||
><el-input-number
|
||||
v-model="row.invoiceAmount"
|
||||
:disabled="readonly"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false" /></template></el-table-column
|
||||
><el-table-column label="匹配金额(含税)" min-width="150"
|
||||
><template #default="{ row }"
|
||||
><el-input-number
|
||||
v-model="row.matchedAmount"
|
||||
:disabled="readonly"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false" /></template></el-table-column
|
||||
><el-table-column label="操作" width="100"
|
||||
><template #default="{ $index }"
|
||||
><el-link v-if="!readonly" type="danger" @click="form.invoices.splice($index, 1)"
|
||||
>删除</el-link
|
||||
></template
|
||||
></el-table-column
|
||||
></el-table
|
||||
>
|
||||
</section>
|
||||
<section class="payment-form-page__section">
|
||||
<div class="payment-form-page__section-title">付款记录</div>
|
||||
<el-table :data="paymentRecords" border
|
||||
><el-table-column type="index" label="序号" width="70" /><el-table-column
|
||||
prop="paidAmount"
|
||||
label="付款金额"
|
||||
min-width="160" /><el-table-column
|
||||
prop="paidDate"
|
||||
label="付款日期"
|
||||
min-width="150" /><el-table-column
|
||||
prop="paymentNo"
|
||||
label="付款单号"
|
||||
min-width="160" /><el-table-column
|
||||
prop="kingdeeBillNo"
|
||||
label="付款凭证"
|
||||
min-width="160"
|
||||
/></el-table>
|
||||
</section>
|
||||
<section class="payment-form-page__section">
|
||||
<div class="payment-form-page__section-title">附件</div>
|
||||
<vehicle-attachment-upload
|
||||
v-model="form.attachments"
|
||||
:readonly="readonly"
|
||||
:multiple="true"
|
||||
:limit="20"
|
||||
:max-size="500"
|
||||
:file-types="attachmentFileTypes"
|
||||
:show-file-list="false"
|
||||
button-text="上传附件"
|
||||
@change="normalizeAttachments"
|
||||
/><el-table :data="form.attachments" border
|
||||
><el-table-column type="index" label="序号" width="70" /><el-table-column
|
||||
label="附件类型"
|
||||
width="160"
|
||||
><template #default="{ row }"
|
||||
><el-select v-model="row.attachmentType" :disabled="readonly"
|
||||
><el-option label="磅单" value="weighing_slip" /><el-option
|
||||
label="结算单"
|
||||
value="settlement" /><el-option label="合同签章文件" value="contract" /><el-option
|
||||
label="特批附件"
|
||||
value="special_approval" /><el-option
|
||||
label="其他"
|
||||
value="other" /></el-select></template></el-table-column
|
||||
><el-table-column prop="originalName" label="文件名" min-width="220" /><el-table-column
|
||||
label="附件描述"
|
||||
min-width="220"
|
||||
><template #default="{ row }"
|
||||
><el-input
|
||||
v-model="row.description"
|
||||
:disabled="readonly"
|
||||
maxlength="200" /></template></el-table-column
|
||||
><el-table-column prop="size" label="文件大小" width="120" /><el-table-column
|
||||
prop="uploadUserName"
|
||||
label="上传人"
|
||||
width="130"
|
||||
/><el-table-column prop="uploadTime" label="上传时间" width="170" /><el-table-column
|
||||
label="操作"
|
||||
width="100"
|
||||
><template #default="{ row, $index }"
|
||||
><el-link type="primary" :href="row.url || row.link" target="_blank">查看</el-link
|
||||
><el-link v-if="!readonly" type="danger" @click="form.attachments.splice($index, 1)"
|
||||
>删除</el-link
|
||||
></template
|
||||
></el-table-column
|
||||
></el-table
|
||||
>
|
||||
</section>
|
||||
<div class="payment-form-page__actions">
|
||||
<el-button @click="goBack">返回</el-button
|
||||
><el-button v-if="!readonly && canSave" @click="saveDraft">保存</el-button
|
||||
><el-button
|
||||
v-if="!readonly && canSave && hasPermission('payment_application_submit')"
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
>提交</el-button
|
||||
>
|
||||
</div>
|
||||
</el-form>
|
||||
<el-dialog
|
||||
v-model="referenceVisible"
|
||||
:title="form.paymentType === 'project_advance' ? '选择项目合同' : '选择结算单'"
|
||||
width="80%"
|
||||
><el-table
|
||||
v-if="form.paymentType === 'project_advance'"
|
||||
:data="contractRows"
|
||||
border
|
||||
@row-click="selectContract"
|
||||
><el-table-column prop="projectName" label="所属项目" /><el-table-column
|
||||
prop="contractNo"
|
||||
label="合同编号" /><el-table-column
|
||||
prop="contractName"
|
||||
label="合同名称" /><el-table-column prop="deptName" label="所属组织" /></el-table
|
||||
><el-tabs v-else v-model="referenceTab"
|
||||
><el-tab-pane label="正式结算单" name="formal"
|
||||
><el-table :data="formalRows" border @row-click="selectFormal"
|
||||
><el-table-column prop="formalSettlementNo" label="结算单号" /><el-table-column
|
||||
prop="projectName"
|
||||
label="所属项目" /><el-table-column
|
||||
prop="contractName"
|
||||
label="合同名称" /><el-table-column
|
||||
prop="settlementAmount"
|
||||
label="结算金额" /></el-table></el-tab-pane
|
||||
><el-tab-pane label="预结算单" name="pre"
|
||||
><el-table :data="preRows" border @row-click="selectPre"
|
||||
><el-table-column prop="preSettlementNo" label="结算单号" /><el-table-column
|
||||
prop="projectName"
|
||||
label="所属项目" /><el-table-column
|
||||
prop="contractName"
|
||||
label="合同名称" /><el-table-column
|
||||
prop="settlementAmount"
|
||||
label="结算金额" /></el-table></el-tab-pane></el-tabs
|
||||
></el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/paymentApplication';
|
||||
import * as billLedgerApi from '@/api/payment/billLedger';
|
||||
import * as formalApi from '@/api/settlement/formalSettlement';
|
||||
import * as preApi from '@/api/settlement/preSettlement';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
paymentNo: '',
|
||||
paymentType: 'project_advance',
|
||||
settlementId: null,
|
||||
preSettlementId: null,
|
||||
projectId: null,
|
||||
projectName: '',
|
||||
deptId: null,
|
||||
deptName: '',
|
||||
contractId: null,
|
||||
contractNo: '',
|
||||
contractName: '',
|
||||
payerName: '',
|
||||
payeeName: '',
|
||||
settlementAmount: 0,
|
||||
payableAmount: 0,
|
||||
billType: '',
|
||||
paymentRatio: 50,
|
||||
appliedAmount: 0,
|
||||
paymentMethod: 'bank_transfer',
|
||||
billLedgerId: null,
|
||||
billNo: '',
|
||||
receiptAccountId: null,
|
||||
receiptAccountName: '',
|
||||
bankName: '',
|
||||
bankAccount: '',
|
||||
applicantName: '',
|
||||
applyDate: '',
|
||||
remark: '',
|
||||
attachments: [],
|
||||
invoices: [],
|
||||
});
|
||||
export default {
|
||||
name: 'PaymentApplicationForm',
|
||||
data() {
|
||||
return {
|
||||
form: emptyForm(),
|
||||
amountSyncing: false,
|
||||
paymentRecords: [],
|
||||
referenceVisible: false,
|
||||
referenceTab: 'formal',
|
||||
formalRows: [],
|
||||
preRows: [],
|
||||
contractRows: [],
|
||||
billOptions: [],
|
||||
billLoading: false,
|
||||
paymentTypeOptions: api.paymentTypeOptions,
|
||||
paymentMethodOptions: api.paymentMethodOptions,
|
||||
attachmentFileTypes: [
|
||||
'pdf',
|
||||
'bmp',
|
||||
'jpeg',
|
||||
'png',
|
||||
'jpg',
|
||||
'doc',
|
||||
'docx',
|
||||
'ppt',
|
||||
'pptx',
|
||||
'xlsx',
|
||||
'xls',
|
||||
'eml',
|
||||
'msg',
|
||||
'zip',
|
||||
'rar',
|
||||
],
|
||||
rules: {
|
||||
paymentType: [{ required: true, message: '请选择付款类型' }],
|
||||
referenceId: [{ validator: this.validateReference, trigger: 'change' }],
|
||||
paymentMethod: [{ required: true, message: '请选择付款方式' }],
|
||||
billLedgerId: [{ validator: this.validateBillLedger, trigger: 'change' }],
|
||||
appliedAmount: [{ validator: this.validateAppliedAmount, trigger: 'change' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
recordId() {
|
||||
return this.$route.query.id || '';
|
||||
},
|
||||
readonly() {
|
||||
return this.$route.query.mode === 'view';
|
||||
},
|
||||
canSave() {
|
||||
const code = this.recordId ? 'payment_application_edit' : 'payment_application_add';
|
||||
return this.hasPermission(code);
|
||||
},
|
||||
billPayment() {
|
||||
return ['bank_draft', 'commercial_draft'].includes(this.form.paymentMethod);
|
||||
},
|
||||
referenceLabel() {
|
||||
return this.form.settlementNo || this.form.preSettlementNo || '';
|
||||
},
|
||||
settlementRows() {
|
||||
if (!this.referenceLabel) return [];
|
||||
return [
|
||||
{
|
||||
settlementNo: this.referenceLabel,
|
||||
settlementAmount: this.form.settlementAmount,
|
||||
payableAmount: this.form.payableAmount,
|
||||
paymentRatio: this.form.paymentRatio,
|
||||
appliedAmount: this.form.appliedAmount,
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'form.paymentRatio'(value) {
|
||||
if (this.amountSyncing || !Number(this.form.payableAmount)) return;
|
||||
this.amountSyncing = true;
|
||||
this.form.appliedAmount = Number(
|
||||
((Number(this.form.payableAmount) * Number(value || 0)) / 100).toFixed(2)
|
||||
);
|
||||
this.$nextTick(() => {
|
||||
this.amountSyncing = false;
|
||||
});
|
||||
},
|
||||
'form.appliedAmount'(value) {
|
||||
if (this.amountSyncing || !Number(this.form.payableAmount)) return;
|
||||
this.amountSyncing = true;
|
||||
this.form.paymentRatio = Number(
|
||||
((Number(value || 0) / Number(this.form.payableAmount)) * 100).toFixed(2)
|
||||
);
|
||||
this.$nextTick(() => {
|
||||
this.amountSyncing = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initialize();
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.permission?.[code] !== false;
|
||||
},
|
||||
validateReference(rule, value, callback) {
|
||||
if (this.form.paymentType === 'progress_advance' && !this.form.preSettlementId) {
|
||||
callback(new Error('请选择预结算单'));
|
||||
return;
|
||||
}
|
||||
if (this.form.paymentType === 'settlement_payment' && !this.form.settlementId) {
|
||||
callback(new Error('请选择正式结算单'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateAppliedAmount(rule, value, callback) {
|
||||
const amount = Number(value);
|
||||
const payableAmount = Number(this.form.payableAmount || 0);
|
||||
if (!Number.isFinite(amount) || amount < 0) {
|
||||
callback(new Error('申请付款金额不能小于0'));
|
||||
return;
|
||||
}
|
||||
if (payableAmount > 0 && amount > payableAmount) {
|
||||
callback(new Error('申请付款金额不能超过可付款金额'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateBillLedger(rule, value, callback) {
|
||||
if (!this.billPayment) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
callback(new Error('请选择可用汇票'));
|
||||
return;
|
||||
}
|
||||
const selected = this.billOptions.find(item => String(item.id) === String(value));
|
||||
if (
|
||||
selected &&
|
||||
Number(this.form.appliedAmount || 0) > Number(selected.availableBalance || 0)
|
||||
) {
|
||||
callback(new Error('申请付款金额不能超过汇票可用余额'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
validateInvoices() {
|
||||
const appliedAmount = Number(this.form.appliedAmount || 0);
|
||||
let matchedTotal = 0;
|
||||
for (const invoice of this.form.invoices || []) {
|
||||
const invoiceAmount = Number(invoice.invoiceAmount || 0);
|
||||
const matchedAmount = Number(invoice.matchedAmount || 0);
|
||||
if (invoiceAmount < 0 || matchedAmount < 0) {
|
||||
throw new Error('发票金额和匹配金额不能小于0');
|
||||
}
|
||||
if (matchedAmount > invoiceAmount) {
|
||||
throw new Error('单张发票匹配金额不能超过发票金额');
|
||||
}
|
||||
matchedTotal += matchedAmount;
|
||||
}
|
||||
if (matchedTotal > appliedAmount) {
|
||||
throw new Error('发票匹配金额合计不能超过申请付款金额');
|
||||
}
|
||||
},
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
async initialize() {
|
||||
if (this.recordId) {
|
||||
const data = this.unwrapData(await api.getDetail(this.recordId));
|
||||
this.form = {
|
||||
...emptyForm(),
|
||||
...data,
|
||||
attachments: this.parse(data.attachmentsJson),
|
||||
invoices: data.invoices || [],
|
||||
};
|
||||
this.paymentRecords = data.paymentRecords || [];
|
||||
if (this.billPayment) await this.loadBillOptions('', data.billLedgerId);
|
||||
} else {
|
||||
this.form.applyDate = this.$dayjs().format('YYYY-MM-DD');
|
||||
this.form.applicantName =
|
||||
this.$store.getters.userInfo?.realName || this.$store.getters.userInfo?.userName || '';
|
||||
}
|
||||
},
|
||||
parse(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value;
|
||||
try {
|
||||
return JSON.parse(value) || [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
handleTypeChange() {
|
||||
if (this.form.paymentType === 'project_advance') {
|
||||
this.form.settlementId = null;
|
||||
this.form.preSettlementId = null;
|
||||
}
|
||||
},
|
||||
async handlePaymentMethodChange() {
|
||||
if (!this.billPayment) {
|
||||
this.form.billLedgerId = null;
|
||||
this.form.billNo = '';
|
||||
this.billOptions = [];
|
||||
return;
|
||||
}
|
||||
await this.loadBillOptions();
|
||||
},
|
||||
async loadBillOptions(keyword = '', selectedId = this.form.billLedgerId) {
|
||||
if (!this.billPayment) return;
|
||||
this.billLoading = true;
|
||||
try {
|
||||
this.billOptions =
|
||||
this.unwrapData(
|
||||
await billLedgerApi.getAvailableOptions(keyword, this.form.deptId, selectedId)
|
||||
) || [];
|
||||
} finally {
|
||||
this.billLoading = false;
|
||||
}
|
||||
},
|
||||
handleBillChange(id) {
|
||||
const selected = this.billOptions.find(item => String(item.id) === String(id));
|
||||
this.form.billNo = selected?.billNo || '';
|
||||
this.$refs.formRef?.validateField('billLedgerId').catch(() => {});
|
||||
},
|
||||
async loadReferences() {
|
||||
const [formalResponse, preResponse, contractResponse] = await Promise.all([
|
||||
formalApi.getList(1, 100, { settlementType: 'payable', approvalStatus: 'approved' }),
|
||||
preApi.getList(1, 100, { settlementType: 'payable', approvalStatus: 'approved' }),
|
||||
formalApi.getContractOptions(''),
|
||||
]);
|
||||
const formal = this.unwrapData(formalResponse);
|
||||
const pre = this.unwrapData(preResponse);
|
||||
this.formalRows = formal.records || [];
|
||||
this.preRows = pre.records || [];
|
||||
this.contractRows = this.unwrapData(contractResponse) || [];
|
||||
},
|
||||
async openReference() {
|
||||
await this.loadReferences();
|
||||
this.referenceVisible = true;
|
||||
},
|
||||
selectContract(row) {
|
||||
Object.assign(this.form, {
|
||||
projectId: row.projectId,
|
||||
projectName: row.projectName,
|
||||
deptId: row.deptId,
|
||||
deptName: row.deptName,
|
||||
contractId: row.id,
|
||||
contractNo: row.contractNo,
|
||||
contractName: row.contractName,
|
||||
payerName: row.payerName,
|
||||
payeeName: row.payeeName,
|
||||
billType: '项目预付',
|
||||
});
|
||||
this.referenceVisible = false;
|
||||
if (this.billPayment) this.loadBillOptions();
|
||||
},
|
||||
selectFormal(row) {
|
||||
Object.assign(this.form, {
|
||||
settlementId: row.id,
|
||||
preSettlementId: null,
|
||||
settlementNo: row.formalSettlementNo,
|
||||
projectName: row.projectName,
|
||||
deptId: row.deptId,
|
||||
deptName: row.deptName,
|
||||
contractName: row.contractName,
|
||||
settlementAmount: row.settlementAmount,
|
||||
payableAmount: Math.max(
|
||||
0,
|
||||
Number(row.settlementAmount || 0) - Number(row.appliedPaymentAmount || 0)
|
||||
),
|
||||
billType: '正式结算单',
|
||||
payeeName: row.payeeName,
|
||||
});
|
||||
this.referenceVisible = false;
|
||||
if (this.billPayment) this.loadBillOptions();
|
||||
},
|
||||
selectPre(row) {
|
||||
Object.assign(this.form, {
|
||||
preSettlementId: row.id,
|
||||
settlementId: null,
|
||||
preSettlementNo: row.preSettlementNo,
|
||||
projectName: row.projectName,
|
||||
deptId: row.deptId,
|
||||
deptName: row.deptName,
|
||||
contractName: row.contractName,
|
||||
settlementAmount: row.settlementAmount,
|
||||
payableAmount: Math.max(
|
||||
0,
|
||||
Number(row.settlementAmount || 0) - Number(row.advanceAppliedAmount || 0)
|
||||
),
|
||||
billType: '预结算单',
|
||||
payeeName: row.payeeName,
|
||||
});
|
||||
this.referenceVisible = false;
|
||||
if (this.billPayment) this.loadBillOptions();
|
||||
},
|
||||
addInvoice() {
|
||||
this.form.invoices.push({
|
||||
settlementNo: this.referenceLabel,
|
||||
invoiceNo: '',
|
||||
invoiceDate: '',
|
||||
invoiceType: '',
|
||||
taxRate: 3,
|
||||
invoiceAmount: 0,
|
||||
matchedAmount: 0,
|
||||
attachmentJson: '',
|
||||
});
|
||||
},
|
||||
normalizeAttachments(files) {
|
||||
const userName =
|
||||
this.$store.getters.userInfo?.realName || this.$store.getters.userInfo?.userName || '';
|
||||
const time = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
this.form.attachments = (files || []).map(file => ({
|
||||
...file,
|
||||
attachmentType: file.attachmentType || 'other',
|
||||
description: file.description || '',
|
||||
uploadUserName: file.uploadUserName || userName,
|
||||
uploadTime: file.uploadTime || time,
|
||||
}));
|
||||
},
|
||||
payload() {
|
||||
const {
|
||||
id,
|
||||
paymentType,
|
||||
settlementId,
|
||||
preSettlementId,
|
||||
projectId,
|
||||
projectName,
|
||||
deptId,
|
||||
deptName,
|
||||
contractId,
|
||||
contractNo,
|
||||
contractName,
|
||||
payerName,
|
||||
payeeName,
|
||||
settlementAmount,
|
||||
payableAmount,
|
||||
billType,
|
||||
paymentRatio,
|
||||
appliedAmount,
|
||||
paymentMethod,
|
||||
billLedgerId,
|
||||
billNo,
|
||||
receiptAccountId,
|
||||
receiptAccountName,
|
||||
bankName,
|
||||
bankAccount,
|
||||
attachments,
|
||||
remark,
|
||||
invoices,
|
||||
} = this.form;
|
||||
return {
|
||||
id,
|
||||
paymentType,
|
||||
settlementId,
|
||||
preSettlementId,
|
||||
projectId,
|
||||
projectName,
|
||||
deptId,
|
||||
deptName,
|
||||
contractId,
|
||||
contractNo,
|
||||
contractName,
|
||||
payerName,
|
||||
payeeName,
|
||||
settlementAmount,
|
||||
payableAmount,
|
||||
billType,
|
||||
paymentRatio,
|
||||
appliedAmount,
|
||||
paymentMethod,
|
||||
billLedgerId,
|
||||
billNo,
|
||||
receiptAccountId,
|
||||
receiptAccountName,
|
||||
bankName,
|
||||
bankAccount,
|
||||
attachmentsJson: JSON.stringify(attachments || []),
|
||||
remark,
|
||||
invoices,
|
||||
};
|
||||
},
|
||||
async saveDraft() {
|
||||
await this.$refs.formRef.validate();
|
||||
if (this.form.paymentType === 'project_advance' && !this.form.projectId) {
|
||||
this.$message.warning('请选择所属项目');
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
this.validateInvoices();
|
||||
} catch (error) {
|
||||
this.$message.warning(error.message);
|
||||
return false;
|
||||
}
|
||||
const data = this.unwrapData(await api.save(this.payload()));
|
||||
this.form.id = data;
|
||||
this.$message.success('保存成功');
|
||||
return true;
|
||||
},
|
||||
async submitForm() {
|
||||
const saved = await this.saveDraft();
|
||||
if (!saved) return;
|
||||
await api.submit({ id: this.form.id });
|
||||
this.$message.success('提交成功');
|
||||
this.goBack();
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/payment/payment-application');
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.payment-form-page__title,
|
||||
.payment-form-page__section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.payment-form-page__title::before,
|
||||
.payment-form-page__section-title::before {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
.payment-form-page__section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.payment-form-page__section-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.payment-form-page__section-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.payment-form-page__actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
padding: 24px 0;
|
||||
border-top: 1px solid #eff1f7;
|
||||
}
|
||||
.payment-form-page :deep(.el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.payment-form-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.payment-form-page :deep(.el-table__body tr:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
:deep(.payment-form-page.basic-container .basic-container__card > .el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,476 @@
|
||||
<template>
|
||||
<basic-container class="payment-page">
|
||||
<section class="payment-page__search">
|
||||
<el-form :model="query" label-position="right" label-width="160px">
|
||||
<div class="payment-page__search-grid">
|
||||
<el-form-item label="单据号"
|
||||
><el-input v-model="query.paymentNo" clearable
|
||||
/></el-form-item>
|
||||
<el-form-item label="日期"
|
||||
><el-date-picker
|
||||
v-model="query.applyDateRange"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD"
|
||||
range-separator="~"
|
||||
start-placeholder="年/月/日"
|
||||
end-placeholder="年/月/日"
|
||||
/></el-form-item>
|
||||
<el-form-item label="客户名称"
|
||||
><el-input v-model="query.payeeName" clearable
|
||||
/></el-form-item>
|
||||
<el-form-item label="项目名称"
|
||||
><el-input v-model="query.projectName" clearable
|
||||
/></el-form-item>
|
||||
<template v-if="searchExpanded">
|
||||
<el-form-item label="所属组织"
|
||||
><el-input v-model="query.deptName" clearable
|
||||
/></el-form-item>
|
||||
<el-form-item label="关联结算单"
|
||||
><el-input v-model="query.settlementNo" clearable
|
||||
/></el-form-item>
|
||||
<el-form-item label="付款类型"
|
||||
><el-select v-model="query.paymentType" clearable
|
||||
><el-option
|
||||
v-for="item in paymentTypeOptions"
|
||||
:key="item.value"
|
||||
v-bind="item" /></el-select
|
||||
></el-form-item>
|
||||
<el-form-item label="审核状态"
|
||||
><el-select v-model="query.approvalStatus" clearable
|
||||
><el-option
|
||||
v-for="item in approvalStatusOptions"
|
||||
:key="item.value"
|
||||
v-bind="item" /></el-select
|
||||
></el-form-item>
|
||||
<el-form-item label="金蝶单据状态"
|
||||
><el-select v-model="query.kingdeeStatus" clearable
|
||||
><el-option
|
||||
v-for="item in kingdeeStatusOptions"
|
||||
:key="item.value"
|
||||
v-bind="item" /></el-select
|
||||
></el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
<div class="payment-page__search-actions">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
<el-button text @click="searchExpanded = !searchExpanded"
|
||||
><el-icon><component :is="searchExpanded ? ArrowUp : ArrowDown" /></el-icon
|
||||
></el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</section>
|
||||
<section class="payment-page__table-panel">
|
||||
<div class="payment-page__toolbar">
|
||||
<div class="payment-page__toolbar-left">
|
||||
<el-button
|
||||
v-if="hasPermission('payment_application_add')"
|
||||
type="primary"
|
||||
@click="openCreate"
|
||||
>新增付款申请</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('payment_application_sync')"
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleSync"
|
||||
>批量同步</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('payment_application_add')"
|
||||
type="primary"
|
||||
plain
|
||||
@click="openSettlementPayment"
|
||||
>申请付尾款</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('payment_application_add')"
|
||||
type="primary"
|
||||
plain
|
||||
@click="openCreate"
|
||||
>预付申请</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="hasPermission('payment_application_export')"
|
||||
type="primary"
|
||||
plain
|
||||
@click="handleExport"
|
||||
>导出</el-button
|
||||
>
|
||||
</div>
|
||||
<el-button text :icon="Refresh" @click="loadTable" />
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="rows" border @selection-change="selection = $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 columns"
|
||||
:key="column.prop"
|
||||
v-bind="column"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="column.link" type="primary" @click="openView(row)">{{
|
||||
row[column.prop]
|
||||
}}</el-link>
|
||||
<el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)">{{
|
||||
row[column.prop] || '-'
|
||||
}}</el-tag>
|
||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="320" fixed="right" align="center">
|
||||
<template #default="{ row }"
|
||||
><div class="payment-page__links">
|
||||
<el-link
|
||||
v-if="hasPermission('payment_application_view')"
|
||||
type="primary"
|
||||
@click="openView(row)"
|
||||
>查看</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('payment_application_edit') &&
|
||||
['draft', 'returned'].includes(row.approvalStatus)
|
||||
"
|
||||
type="primary"
|
||||
@click="openEdit(row)"
|
||||
>编辑</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="hasPermission('payment_application_submit') && row.approvalStatus === 'draft'"
|
||||
type="primary"
|
||||
@click="handleSubmit(row)"
|
||||
>提交</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('payment_application_approve') && row.approvalStatus === 'reviewing'
|
||||
"
|
||||
type="primary"
|
||||
@click="handleApprove(row)"
|
||||
>通过</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('payment_application_approve') && row.approvalStatus === 'reviewing'
|
||||
"
|
||||
type="danger"
|
||||
@click="handleReturn(row)"
|
||||
>驳回</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="hasPermission('payment_application_view') && row.approvalStatus !== 'draft'"
|
||||
type="primary"
|
||||
@click="openFlow(row)"
|
||||
>流程</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('payment_application_void') && row.approvalStatus === 'approved'
|
||||
"
|
||||
type="danger"
|
||||
@click="handleVoid(row)"
|
||||
>作废</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="hasPermission('payment_application_delete') && row.approvalStatus === 'draft'"
|
||||
type="danger"
|
||||
@click="handleDelete(row)"
|
||||
>删除</el-link
|
||||
>
|
||||
</div></template
|
||||
>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="payment-page__pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="page.current"
|
||||
v-model:page-size="page.size"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadTable"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
<el-dialog v-model="flowDialog.visible" title="审批流程" width="620px" append-to-body>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="付款申请单号">
|
||||
{{ flowDialog.row.paymentNo || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审核状态">
|
||||
{{ flowDialog.row.approvalStatusName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前节点">
|
||||
{{ flowDialog.row.currentNode || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="当前处理人">
|
||||
{{ flowDialog.row.currentProcessor || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ArrowDown, ArrowUp, Refresh } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/paymentApplication';
|
||||
import { paymentApplicationTableColumns } from '@/option/payment/paymentApplication';
|
||||
|
||||
const emptyQuery = () => ({
|
||||
paymentNo: '',
|
||||
applyDateRange: [],
|
||||
payeeName: '',
|
||||
projectName: '',
|
||||
deptName: '',
|
||||
settlementNo: '',
|
||||
paymentType: '',
|
||||
approvalStatus: '',
|
||||
kingdeeStatus: '',
|
||||
});
|
||||
export default {
|
||||
name: 'PaymentApplication',
|
||||
data() {
|
||||
return {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
Refresh,
|
||||
query: emptyQuery(),
|
||||
searchExpanded: false,
|
||||
paymentTypeOptions: api.paymentTypeOptions,
|
||||
approvalStatusOptions: api.approvalStatusOptions,
|
||||
kingdeeStatusOptions: api.kingdeeStatusOptions,
|
||||
columns: paymentApplicationTableColumns,
|
||||
rows: [],
|
||||
selection: [],
|
||||
loading: false,
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
flowDialog: { visible: false, row: {} },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission']),
|
||||
visibleSearchFields() {
|
||||
return this.searchExpanded ? this.columns : this.columns.slice(0, 4);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadTable();
|
||||
},
|
||||
methods: {
|
||||
hasPermission(code) {
|
||||
return this.permission?.[code] !== false;
|
||||
},
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
buildParams() {
|
||||
const params = { ...this.query };
|
||||
const range = params.applyDateRange || [];
|
||||
delete params.applyDateRange;
|
||||
if (range.length === 2) {
|
||||
params.applyStartDate = range[0];
|
||||
params.applyEndDate = range[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
async loadTable() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const data = this.unwrapData(
|
||||
await api.getList(this.page.current, this.page.size, this.buildParams())
|
||||
);
|
||||
this.rows = data.records || [];
|
||||
this.page.total = data.total || 0;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
resetSearch() {
|
||||
this.query = emptyQuery();
|
||||
this.handleSearch();
|
||||
},
|
||||
handleSizeChange() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
openCreate() {
|
||||
this.$router.push({ path: '/payment/payment-application/form', query: { mode: 'add' } });
|
||||
},
|
||||
openEdit(row) {
|
||||
this.$router.push({
|
||||
path: '/payment/payment-application/form',
|
||||
query: { mode: 'edit', id: row.id },
|
||||
});
|
||||
},
|
||||
openView(row) {
|
||||
this.$router.push({
|
||||
path: '/payment/payment-application/form',
|
||||
query: { mode: 'view', id: row.id },
|
||||
});
|
||||
},
|
||||
openSettlementPayment() {
|
||||
this.openCreate();
|
||||
},
|
||||
selectedOne(action) {
|
||||
if (this.selection.length !== 1) {
|
||||
this.$message.warning(`${action}需选择一条付款申请`);
|
||||
return null;
|
||||
}
|
||||
return this.selection[0];
|
||||
},
|
||||
async handleDelete(row) {
|
||||
await this.$confirm('确认删除该付款申请?', '提示', { type: 'warning' });
|
||||
await api.remove(row.id);
|
||||
this.$message.success('删除成功');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleSubmit(row) {
|
||||
await api.submit({ id: row.id });
|
||||
this.$message.success('提交成功');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleApprove(row) {
|
||||
await api.approve({ id: row.id });
|
||||
this.$message.success('审批通过');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleReturn(row) {
|
||||
const { value } = await this.$prompt('请输入驳回原因', '审批驳回');
|
||||
await api.returnBill({ id: row.id, reason: value });
|
||||
this.$message.success('已驳回');
|
||||
this.loadTable();
|
||||
},
|
||||
openFlow(row) {
|
||||
this.flowDialog.row = row;
|
||||
this.flowDialog.visible = true;
|
||||
},
|
||||
async handleVoid(row) {
|
||||
const { value } = await this.$prompt('请输入作废原因', '作废付款申请');
|
||||
await api.voidBill({ id: row.id, reason: value });
|
||||
this.$message.success('作废成功');
|
||||
this.loadTable();
|
||||
},
|
||||
async handleSyncRow(row) {
|
||||
const data = this.unwrapData(await api.syncKingdee(row.id));
|
||||
this.$message.success(`已生成金蝶付款单:${data}`);
|
||||
this.loadTable();
|
||||
},
|
||||
async handleSync() {
|
||||
const rows = this.selection.filter(row => row.approvalStatus === 'approved');
|
||||
if (!rows.length) {
|
||||
this.$message.warning('请选择至少一条审批通过的付款申请');
|
||||
return;
|
||||
}
|
||||
await Promise.all(rows.map(row => api.syncKingdee(row.id)));
|
||||
this.$message.success(`已同步${rows.length}条付款申请`);
|
||||
this.loadTable();
|
||||
},
|
||||
handleExport() {
|
||||
const headers = this.columns.map(item => item.label);
|
||||
const rows = this.rows.map(row => this.columns.map(item => row[item.prop] || ''));
|
||||
const csv = [headers, ...rows]
|
||||
.map(item => item.map(value => `"${String(value).replaceAll('"', '""')}"`).join(','))
|
||||
.join('\n');
|
||||
const blob = new Blob([`\ufeff${csv}`], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = `付款申请${this.$dayjs().format('YYYY-MM-DD HH-mm-ss')}.csv`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
},
|
||||
statusType(status) {
|
||||
return (
|
||||
{ approved: 'success', reviewing: 'warning', returned: 'danger', voided: 'info' }[status] ||
|
||||
''
|
||||
);
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : value;
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.payment-page__search {
|
||||
padding: 12px 12px 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.payment-page__search-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px 24px;
|
||||
}
|
||||
.payment-page__search :deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.payment-page__search :deep(.el-input),
|
||||
.payment-page__search :deep(.el-select),
|
||||
.payment-page__search :deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
}
|
||||
.payment-page__search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.payment-page__table-panel {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.payment-page__toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.payment-page__toolbar-left {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.payment-page__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.payment-page__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.payment-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.payment-page :deep(.el-table__body tr:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
:deep(.payment-page.basic-container .basic-container__card > .el-card__body) {
|
||||
padding: 0;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.payment-page__search-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
@media (max-width: 760px) {
|
||||
.payment-page__search-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<basic-container class="claim-record-form-page">
|
||||
<el-form :model="form" label-position="right" label-width="auto">
|
||||
<section class="claim-record-form-page__section">
|
||||
<div class="claim-record-form-page__section-title">收款信息</div>
|
||||
<el-row :gutter="32">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="认领通知单">
|
||||
<el-input v-model="form.receiptNoticeNo" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="付款人"
|
||||
><el-input v-model="form.payerName" disabled
|
||||
/></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="收款金额">
|
||||
<el-input :model-value="formatMoney(form.receiptAmount)" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="交易时间">
|
||||
<el-input v-model="form.transactionTime" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="对方户名">
|
||||
<el-input v-model="form.counterpartyName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="对方开户行">
|
||||
<el-input v-model="form.counterpartyBank" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="对方账号">
|
||||
<el-input v-model="form.counterpartyAccount" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="明细流水号">
|
||||
<el-input v-model="form.detailSerialNo" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="已认领金额">
|
||||
<el-input :model-value="formatMoney(form.claimAmount)" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="摘要"><el-input v-model="form.summary" disabled /></el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="关联结算单">
|
||||
<el-input v-model="form.associatedSettlementNos" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="认领人">
|
||||
<el-input v-model="form.claimerName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="认领日期">
|
||||
<el-input v-model="form.claimDate" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="认领人部门">
|
||||
<el-input v-model="form.claimerDeptName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
|
||||
<section class="claim-record-form-page__section">
|
||||
<div class="claim-record-form-page__section-title">结算信息</div>
|
||||
<el-table :data="form.settlements" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="结算单号" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click="openSettlementDetail(row)">
|
||||
{{ row.formalSettlementNo }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结算总金额" min-width="150" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.settlementAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已认领收款金额" min-width="170" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.claimedReceiptAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分摊收款金额" min-width="170" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.allocatedReceiptAmount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="claim-record-form-page__section">
|
||||
<div class="claim-record-form-page__section-heading">
|
||||
<div class="claim-record-form-page__section-title">附件信息</div>
|
||||
<el-button
|
||||
v-if="form.attachments.length"
|
||||
type="primary"
|
||||
:icon="Download"
|
||||
@click="downloadAttachments"
|
||||
>批量下载</el-button
|
||||
>
|
||||
</div>
|
||||
<el-table :data="form.attachments" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="文件名" min-width="220">
|
||||
<template #default="{ row }">{{ row.originalName || row.name || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="附件描述" min-width="240" />
|
||||
<el-table-column label="文件大小" width="120">
|
||||
<template #default="{ row }">{{ displayFileSize(row.size) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploadUserName" label="上传人" width="140" />
|
||||
<el-table-column prop="uploadTime" label="上传时间" width="170" />
|
||||
<el-table-column label="操作" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="claim-record-form-page__links">
|
||||
<el-link v-if="row.url || row.link" type="primary" @click="viewAttachment(row)">
|
||||
查看
|
||||
</el-link>
|
||||
<el-link
|
||||
v-if="form.claimStatus === 'claimed'"
|
||||
type="danger"
|
||||
@click="removeAttachment(row)"
|
||||
>删除</el-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<vehicle-attachment-upload
|
||||
v-if="form.claimStatus === 'claimed'"
|
||||
v-model="form.attachments"
|
||||
:multiple="true"
|
||||
:limit="20"
|
||||
:max-size="500"
|
||||
:file-types="attachmentFileTypes"
|
||||
:show-file-list="false"
|
||||
button-text="上传附件"
|
||||
class="claim-record-form-page__uploader"
|
||||
@change="handleAttachmentChange"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="claim-record-form-page__section">
|
||||
<div class="claim-record-form-page__section-title">备注</div>
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" maxlength="200" disabled />
|
||||
</section>
|
||||
|
||||
<div class="claim-record-form-page__actions">
|
||||
<el-button @click="goBack">关闭</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Download } from '@element-plus/icons-vue';
|
||||
import * as api from '@/api/payment/receiptClaimRecord';
|
||||
|
||||
const emptyForm = () => ({
|
||||
receiptNoticeNo: '',
|
||||
payerName: '',
|
||||
receiptAmount: 0,
|
||||
transactionTime: '',
|
||||
counterpartyName: '',
|
||||
counterpartyBank: '',
|
||||
counterpartyAccount: '',
|
||||
detailSerialNo: '',
|
||||
claimAmount: 0,
|
||||
summary: '',
|
||||
associatedSettlementNos: '',
|
||||
claimerName: '',
|
||||
claimDate: '',
|
||||
claimerDeptName: '',
|
||||
claimStatus: '',
|
||||
settlements: [],
|
||||
attachments: [],
|
||||
remark: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'ReceiptClaimRecordForm',
|
||||
data() {
|
||||
return {
|
||||
Download,
|
||||
form: emptyForm(),
|
||||
attachmentFileTypes: [
|
||||
'pdf',
|
||||
'bmp',
|
||||
'jpeg',
|
||||
'png',
|
||||
'jpg',
|
||||
'doc',
|
||||
'docx',
|
||||
'ppt',
|
||||
'pptx',
|
||||
'xlsx',
|
||||
'xls',
|
||||
'eml',
|
||||
'msg',
|
||||
'zip',
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
recordId() {
|
||||
return this.$route.query.id || '';
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initialize();
|
||||
},
|
||||
methods: {
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
async initialize() {
|
||||
if (!this.recordId) {
|
||||
this.$message.error('缺少认领记录');
|
||||
this.goBack();
|
||||
return;
|
||||
}
|
||||
const data = this.unwrapData(await api.getDetail(this.recordId));
|
||||
this.form = {
|
||||
...emptyForm(),
|
||||
...data,
|
||||
settlements: data.settlements || [],
|
||||
attachments: this.parseAttachments(data.attachmentsJson),
|
||||
};
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value;
|
||||
try {
|
||||
const result = JSON.parse(value);
|
||||
return Array.isArray(result) ? result : [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
downloadAttachments() {
|
||||
this.form.attachments.forEach(file => {
|
||||
const url = file.url || file.link;
|
||||
if (!url) return;
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = file.originalName || file.name || '';
|
||||
anchor.target = '_blank';
|
||||
anchor.click();
|
||||
});
|
||||
},
|
||||
viewAttachment(row) {
|
||||
window.open(row.url || row.link, '_blank', 'noopener');
|
||||
},
|
||||
async handleAttachmentChange(files) {
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
const userName = userInfo.realName || userInfo.userName || '';
|
||||
const uploadTime = 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 || uploadTime,
|
||||
}));
|
||||
await this.persistAttachments('附件上传成功');
|
||||
},
|
||||
async removeAttachment(row) {
|
||||
await this.$confirm('确定删除该附件吗?', '删除附件', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
});
|
||||
this.form.attachments = this.form.attachments.filter(item => item !== row);
|
||||
await this.persistAttachments('附件删除成功');
|
||||
},
|
||||
async persistAttachments(message) {
|
||||
await api.updateAttachments({
|
||||
id: this.recordId,
|
||||
attachmentsJson: JSON.stringify(this.form.attachments || []),
|
||||
});
|
||||
this.$message.success(message);
|
||||
},
|
||||
openSettlementDetail(row) {
|
||||
if (!row.formalSettlementId) return;
|
||||
this.$router.push({
|
||||
path: '/settlement/formal-settlement/form',
|
||||
query: { mode: 'view', id: row.formalSettlementId, name: '查看正式结算' },
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/payment/receipt-claim-record');
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
displayFileSize(size) {
|
||||
if (size === null || size === undefined || size === '') return '-';
|
||||
if (typeof size === 'string' && /[a-z]/i.test(size)) return size;
|
||||
const bytes = Number(size);
|
||||
if (!Number.isFinite(bytes)) return size;
|
||||
if (bytes < 1024) return `${bytes}B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}KB`;
|
||||
return `${(bytes / 1024 / 1024).toFixed(2)}MB`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.claim-record-form-page__section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.claim-record-form-page__section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.claim-record-form-page__section-title::before {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
.claim-record-form-page__section-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.claim-record-form-page__section-heading .claim-record-form-page__section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.claim-record-form-page :deep(.el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.claim-record-form-page__actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 16px 0 8px;
|
||||
}
|
||||
.claim-record-form-page__links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.claim-record-form-page__uploader {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.claim-record-form-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.claim-record-form-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<basic-container class="claim-record-page">
|
||||
<section class="claim-record-page__search">
|
||||
<el-form :model="query" label-position="right" label-width="160px" @submit.prevent>
|
||||
<div class="claim-record-page__search-grid">
|
||||
<el-form-item label="认领通知单">
|
||||
<el-input v-model="query.receiptNoticeNo" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="对方户名">
|
||||
<el-input v-model="query.counterpartyName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="对方开户行">
|
||||
<el-input v-model="query.counterpartyBank" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="对方账号">
|
||||
<el-input v-model="query.counterpartyAccount" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="摘要">
|
||||
<el-input v-model="query.summary" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="状态">
|
||||
<el-select v-model="query.claimStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in claimStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="交易日期">
|
||||
<el-date-picker
|
||||
v-model="query.transactionDateRange"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD"
|
||||
range-separator="~"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="认领人">
|
||||
<el-input v-model="query.claimerName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="认领日期">
|
||||
<el-date-picker
|
||||
v-model="query.claimDateRange"
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD"
|
||||
range-separator="~"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="所属组织">
|
||||
<el-input v-model="query.claimerDeptName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="金蝶单据状态">
|
||||
<el-select v-model="query.kingdeeBillStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in kingdeeBillStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="claim-record-page__search-actions">
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
<el-tooltip :content="searchExpanded ? '折叠' : '展开'" placement="top">
|
||||
<el-button text @click="searchExpanded = !searchExpanded">
|
||||
<el-icon><component :is="searchExpanded ? ArrowUp : ArrowDown" /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form>
|
||||
</section>
|
||||
|
||||
<section class="claim-record-page__table-panel">
|
||||
<el-table v-loading="loading" :data="rows" border>
|
||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in columns"
|
||||
:key="column.prop"
|
||||
v-bind="column"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="column.link" type="primary" @click="openDetail(row)">
|
||||
{{ displayValue(row[column.prop]) }}
|
||||
</el-link>
|
||||
<el-tag v-else-if="column.status" :type="statusType(row.claimStatus)">
|
||||
{{ displayValue(row[column.prop]) }}
|
||||
</el-tag>
|
||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link
|
||||
v-if="hasPermission('receipt_claim_record_void') && row.claimStatus === 'claimed'"
|
||||
type="danger"
|
||||
@click="handleVoid(row)"
|
||||
>作废</el-link
|
||||
>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="claim-record-page__pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="page.current"
|
||||
v-model:page-size="page.size"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadTable"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/receiptClaimRecord';
|
||||
import { receiptClaimRecordTableColumns } from '@/option/payment/receiptClaimRecord';
|
||||
|
||||
const emptyQuery = () => ({
|
||||
receiptNoticeNo: '',
|
||||
counterpartyName: '',
|
||||
counterpartyBank: '',
|
||||
counterpartyAccount: '',
|
||||
summary: '',
|
||||
claimStatus: '',
|
||||
transactionDateRange: [],
|
||||
claimerName: '',
|
||||
claimDateRange: [],
|
||||
claimerDeptName: '',
|
||||
kingdeeBillStatus: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'ReceiptClaimRecord',
|
||||
data() {
|
||||
return {
|
||||
ArrowDown,
|
||||
ArrowUp,
|
||||
query: emptyQuery(),
|
||||
searchExpanded: false,
|
||||
claimStatusOptions: api.claimStatusOptions,
|
||||
kingdeeBillStatusOptions: api.kingdeeBillStatusOptions,
|
||||
columns: receiptClaimRecordTableColumns,
|
||||
rows: [],
|
||||
loading: false,
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo?.authority;
|
||||
return Array.isArray(authority)
|
||||
? authority.includes('admin')
|
||||
: String(authority || '').includes('admin');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadTable();
|
||||
},
|
||||
methods: {
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
queryParams() {
|
||||
const transactionRange = this.query.transactionDateRange || [];
|
||||
const claimRange = this.query.claimDateRange || [];
|
||||
return {
|
||||
...this.query,
|
||||
transactionDateRange: undefined,
|
||||
claimDateRange: undefined,
|
||||
transactionStartDate: transactionRange[0],
|
||||
transactionEndDate: transactionRange[1],
|
||||
claimStartDate: claimRange[0],
|
||||
claimEndDate: claimRange[1],
|
||||
};
|
||||
},
|
||||
async loadTable() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const data = this.unwrapData(
|
||||
await api.getList(this.page.current, this.page.size, this.queryParams())
|
||||
);
|
||||
this.rows = data.records || [];
|
||||
this.page.total = Number(data.total || 0);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
resetSearch() {
|
||||
this.query = emptyQuery();
|
||||
this.handleSearch();
|
||||
},
|
||||
handleSizeChange() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
openDetail(row) {
|
||||
this.$router.push({ path: '/payment/receipt-claim-record/form', query: { id: row.id } });
|
||||
},
|
||||
async handleVoid(row) {
|
||||
await this.$confirm(
|
||||
'作废后将回退本次认领金额,并生成审核通过状态的金蝶认领冲单,是否继续?',
|
||||
'作废认领记录',
|
||||
{ type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }
|
||||
);
|
||||
const kingdeeBillNo = this.unwrapData(await api.voidClaim(row.id));
|
||||
this.$message.success(`作废成功,金蝶冲单号:${kingdeeBillNo}`);
|
||||
await this.loadTable();
|
||||
},
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission?.[code], false);
|
||||
},
|
||||
statusType(status) {
|
||||
return status === 'voided' ? 'info' : 'success';
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : value;
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.claim-record-page__search {
|
||||
padding: 12px 12px 4px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.claim-record-page__search-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
column-gap: 16px;
|
||||
row-gap: 8px;
|
||||
}
|
||||
.claim-record-page__search :deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.claim-record-page__search :deep(.el-input),
|
||||
.claim-record-page__search :deep(.el-select),
|
||||
.claim-record-page__search :deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
}
|
||||
.claim-record-page__search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
.claim-record-page__table-panel {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.claim-record-page__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.claim-record-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.claim-record-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.claim-record-page__search-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,537 @@
|
||||
<template>
|
||||
<basic-container class="receipt-claim-form-page">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="receipt-claim-form-page__form"
|
||||
>
|
||||
<section class="receipt-claim-form-page__section">
|
||||
<div class="receipt-claim-form-page__section-title">收款信息</div>
|
||||
<el-row :gutter="32">
|
||||
<el-col :span="12"
|
||||
><el-form-item label="认领通知单"
|
||||
><el-input v-model="form.receiptNoticeNo" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="付款人"
|
||||
><el-input v-model="form.payerName" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="收款金额"
|
||||
><el-input :model-value="formatMoney(form.receiptAmount)" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="交易时间"
|
||||
><el-input v-model="form.transactionTime" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="对方户名"
|
||||
><el-input v-model="form.counterpartyName" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="对方开户行"
|
||||
><el-input v-model="form.counterpartyBank" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="对方账号"
|
||||
><el-input v-model="form.counterpartyAccount" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="明细流水号"
|
||||
><el-input v-model="form.detailSerialNo" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="已认领金额"
|
||||
><el-input :model-value="formatMoney(form.claimedAmount)" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="摘要"><el-input v-model="form.summary" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="关联结算单" prop="settlementIds"
|
||||
><el-input
|
||||
:model-value="settlementLabel"
|
||||
readonly
|
||||
placeholder="请选择已审批通过的应收正式结算单"
|
||||
><template #append
|
||||
><el-button
|
||||
:icon="Search"
|
||||
title="选择结算单"
|
||||
@click="openSettlementDialog" /></template></el-input></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="认领人"
|
||||
><el-input v-model="form.claimerName" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="认领日期"
|
||||
><el-input v-model="form.claimDate" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="12"
|
||||
><el-form-item label="认领人部门"
|
||||
><el-input v-model="form.claimerDeptName" disabled /></el-form-item
|
||||
></el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
|
||||
<section class="receipt-claim-form-page__section">
|
||||
<div class="receipt-claim-form-page__section-title">结算信息</div>
|
||||
<el-table :data="form.settlements" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="结算单号" min-width="180" align="center"
|
||||
><template #default="{ row }"
|
||||
><el-link type="primary" @click="openSettlementDetail(row)">{{
|
||||
row.formalSettlementNo
|
||||
}}</el-link></template
|
||||
></el-table-column
|
||||
>
|
||||
<el-table-column label="结算总金额" min-width="150" align="right"
|
||||
><template #default="{ row }">{{
|
||||
formatMoney(row.settlementAmount)
|
||||
}}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="已认领收款金额" min-width="160" align="right"
|
||||
><template #default="{ row }">{{
|
||||
formatMoney(row.claimedReceiptAmount)
|
||||
}}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="分摊收款金额" min-width="180"
|
||||
><template #default="{ row }"
|
||||
><el-input-number
|
||||
v-model="row.allocatedReceiptAmount"
|
||||
:min="0"
|
||||
:max="remainingAmount(row)"
|
||||
:precision="2"
|
||||
:controls="false" /></template
|
||||
></el-table-column>
|
||||
</el-table>
|
||||
</section>
|
||||
|
||||
<section class="receipt-claim-form-page__section">
|
||||
<div class="receipt-claim-form-page__section-heading">
|
||||
<div class="receipt-claim-form-page__section-title">附件信息</div>
|
||||
<el-button
|
||||
v-if="form.attachments.length"
|
||||
type="primary"
|
||||
plain
|
||||
:icon="Download"
|
||||
@click="downloadAttachments"
|
||||
>批量下载</el-button
|
||||
>
|
||||
</div>
|
||||
<el-table :data="form.attachments" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="文件名" min-width="220"
|
||||
><template #default="{ row }">{{
|
||||
row.originalName || row.name || '-'
|
||||
}}</template></el-table-column
|
||||
>
|
||||
<el-table-column label="附件描述" min-width="240"
|
||||
><template #default="{ row }"
|
||||
><el-input v-model="row.description" maxlength="200" /></template
|
||||
></el-table-column>
|
||||
<el-table-column prop="size" label="文件大小" width="120" /><el-table-column
|
||||
prop="uploadUserName"
|
||||
label="上传人"
|
||||
width="140"
|
||||
/><el-table-column prop="uploadTime" label="上传时间" width="170" />
|
||||
<el-table-column label="操作" width="120" align="center"
|
||||
><template #default="{ row, $index }"
|
||||
><div class="receipt-claim-form-page__links">
|
||||
<el-link type="primary" :href="row.url || row.link" target="_blank">查看</el-link
|
||||
><el-link type="danger" @click="form.attachments.splice($index, 1)">删除</el-link>
|
||||
</div></template
|
||||
></el-table-column
|
||||
>
|
||||
</el-table>
|
||||
<vehicle-attachment-upload
|
||||
v-model="form.attachments"
|
||||
:multiple="true"
|
||||
:limit="20"
|
||||
:max-size="500"
|
||||
:file-types="attachmentFileTypes"
|
||||
:show-file-list="false"
|
||||
button-text="上传附件"
|
||||
class="receipt-claim-form-page__uploader"
|
||||
@change="normalizeAttachments"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="receipt-claim-form-page__section">
|
||||
<div class="receipt-claim-form-page__section-title">备注</div>
|
||||
<el-form-item class="receipt-claim-form-page__remark"
|
||||
><el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/></el-form-item>
|
||||
</section>
|
||||
<div class="receipt-claim-form-page__actions">
|
||||
<el-button type="primary" :loading="submitting" @click="submitClaim">确认</el-button
|
||||
><el-button :disabled="submitting" @click="goBack">取消</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<el-dialog
|
||||
v-model="settlementDialog.visible"
|
||||
title="选择应收正式结算单"
|
||||
width="86%"
|
||||
append-to-body
|
||||
>
|
||||
<div class="receipt-claim-form-page__dialog-search">
|
||||
<el-input
|
||||
v-model="settlementDialog.keyword"
|
||||
clearable
|
||||
placeholder="结算单号、项目或合同"
|
||||
@keyup.enter="loadSettlementCandidates"
|
||||
/><el-button type="primary" @click="loadSettlementCandidates">查询</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="settlementDialog.loading"
|
||||
:data="settlementDialog.rows"
|
||||
border
|
||||
@selection-change="settlementDialog.selection = $event"
|
||||
>
|
||||
<el-table-column type="selection" width="52" align="center" /><el-table-column
|
||||
prop="formalSettlementNo"
|
||||
label="结算单号"
|
||||
min-width="170"
|
||||
/><el-table-column prop="projectName" label="所属项目" min-width="150" /><el-table-column
|
||||
prop="deptName"
|
||||
label="所属组织"
|
||||
min-width="150"
|
||||
/><el-table-column prop="payerName" label="付款方" min-width="150" /><el-table-column
|
||||
prop="payeeName"
|
||||
label="收款方"
|
||||
min-width="150"
|
||||
/><el-table-column label="结算总金额" min-width="140" align="right"
|
||||
><template #default="{ row }">{{
|
||||
formatMoney(row.settlementAmount)
|
||||
}}</template></el-table-column
|
||||
><el-table-column label="已认领收款金额" min-width="150" align="right"
|
||||
><template #default="{ row }">{{
|
||||
formatMoney(row.claimedReceiptAmount)
|
||||
}}</template></el-table-column
|
||||
>
|
||||
</el-table>
|
||||
<template #footer
|
||||
><el-button @click="settlementDialog.visible = false">取消</el-button
|
||||
><el-button type="primary" @click="confirmSettlements">确定</el-button></template
|
||||
>
|
||||
</el-dialog>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Download, Search } from '@element-plus/icons-vue';
|
||||
import * as api from '@/api/payment/receiptFlow';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
receiptNoticeNo: '',
|
||||
payerName: '',
|
||||
receiptAmount: 0,
|
||||
counterpartyName: '',
|
||||
counterpartyAccount: '',
|
||||
counterpartyBank: '',
|
||||
summary: '',
|
||||
transactionTime: '',
|
||||
detailSerialNo: '',
|
||||
claimedAmount: 0,
|
||||
claimerName: '',
|
||||
claimerDeptName: '',
|
||||
claimDate: '',
|
||||
settlementIds: [],
|
||||
settlements: [],
|
||||
attachments: [],
|
||||
remark: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'ReceiptFlowForm',
|
||||
data() {
|
||||
return {
|
||||
Download,
|
||||
Search,
|
||||
form: emptyForm(),
|
||||
submitting: false,
|
||||
attachmentFileTypes: [
|
||||
'pdf',
|
||||
'bmp',
|
||||
'jpeg',
|
||||
'png',
|
||||
'jpg',
|
||||
'doc',
|
||||
'docx',
|
||||
'ppt',
|
||||
'pptx',
|
||||
'xlsx',
|
||||
'xls',
|
||||
'eml',
|
||||
'msg',
|
||||
'zip',
|
||||
],
|
||||
settlementDialog: { visible: false, loading: false, keyword: '', rows: [], selection: [] },
|
||||
rules: {
|
||||
settlementIds: [
|
||||
{ required: true, message: '请选择应收正式结算单', trigger: 'change' },
|
||||
{ validator: this.validateSettlements, trigger: 'change' },
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
flowId() {
|
||||
return this.$route.query.id || '';
|
||||
},
|
||||
settlementLabel() {
|
||||
return this.form.settlements
|
||||
.map(item => item.formalSettlementNo)
|
||||
.filter(Boolean)
|
||||
.join(',');
|
||||
},
|
||||
allocatedTotal() {
|
||||
return this.form.settlements.reduce(
|
||||
(total, item) => total + Number(item.allocatedReceiptAmount || 0),
|
||||
0
|
||||
);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initialize();
|
||||
},
|
||||
methods: {
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
async initialize() {
|
||||
if (!this.flowId) {
|
||||
this.$message.error('缺少收款流水');
|
||||
this.goBack();
|
||||
return;
|
||||
}
|
||||
const data = this.unwrapData(await api.getDetail(this.flowId));
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
this.form = {
|
||||
...emptyForm(),
|
||||
...data,
|
||||
claimerName: userInfo.realName || userInfo.userName || data.claimerName || '',
|
||||
claimerDeptName: userInfo.deptName || userInfo.dept_name || data.claimerDeptName || '',
|
||||
claimDate: this.$dayjs().format('YYYY-MM-DD'),
|
||||
settlementIds: [],
|
||||
settlements: [],
|
||||
attachments: [],
|
||||
};
|
||||
},
|
||||
validateSettlements(rule, value, callback) {
|
||||
this.form.settlements.length ? callback() : callback(new Error('请选择应收正式结算单'));
|
||||
},
|
||||
async openSettlementDialog() {
|
||||
this.settlementDialog.visible = true;
|
||||
await this.loadSettlementCandidates();
|
||||
},
|
||||
async loadSettlementCandidates() {
|
||||
this.settlementDialog.loading = true;
|
||||
try {
|
||||
this.settlementDialog.rows =
|
||||
this.unwrapData(
|
||||
await api.getSettlementCandidates(this.settlementDialog.keyword, this.flowId)
|
||||
) || [];
|
||||
} finally {
|
||||
this.settlementDialog.loading = false;
|
||||
}
|
||||
},
|
||||
confirmSettlements() {
|
||||
const rows = this.settlementDialog.selection;
|
||||
if (!rows.length) {
|
||||
this.$message.warning('请至少选择一张应收正式结算单');
|
||||
return;
|
||||
}
|
||||
const first = rows[0];
|
||||
if (
|
||||
rows.some(
|
||||
item =>
|
||||
String(item.projectId) !== String(first.projectId) ||
|
||||
String(item.deptId) !== String(first.deptId) ||
|
||||
item.payerName !== first.payerName ||
|
||||
item.payeeName !== first.payeeName
|
||||
)
|
||||
) {
|
||||
this.$message.warning('关联结算单必须属于同一项目、组织及收付款方');
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.form.counterpartyName &&
|
||||
rows.some(item => item.payerName && item.payerName !== this.form.counterpartyName)
|
||||
) {
|
||||
this.$message.warning('对方户名与结算单付款方不一致');
|
||||
return;
|
||||
}
|
||||
this.form.settlements = rows.map(item => ({
|
||||
...item,
|
||||
formalSettlementId: item.id,
|
||||
settlementId: item.id,
|
||||
allocatedReceiptAmount: 0,
|
||||
}));
|
||||
this.form.settlementIds = this.form.settlements.map(item => item.formalSettlementId);
|
||||
this.settlementDialog.visible = false;
|
||||
this.$nextTick(() => this.$refs.formRef?.validateField('settlementIds'));
|
||||
},
|
||||
remainingAmount(row) {
|
||||
return Math.max(Number(row.settlementAmount || 0) - Number(row.claimedReceiptAmount || 0), 0);
|
||||
},
|
||||
normalizeAttachments(files) {
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
const userName = userInfo.realName || 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,
|
||||
}));
|
||||
},
|
||||
downloadAttachments() {
|
||||
this.form.attachments.forEach(file => {
|
||||
const url = file.url || file.link;
|
||||
if (!url) return;
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = file.originalName || file.name || '';
|
||||
anchor.target = '_blank';
|
||||
anchor.click();
|
||||
});
|
||||
},
|
||||
async submitClaim() {
|
||||
await this.$refs.formRef.validate();
|
||||
const amount = Number(this.form.receiptAmount || 0);
|
||||
if (amount <= 0 || this.allocatedTotal <= 0) {
|
||||
this.$message.warning('分摊收款金额必须大于0');
|
||||
return;
|
||||
}
|
||||
if (this.allocatedTotal > amount - Number(this.form.claimedAmount || 0) + 0.001) {
|
||||
this.$message.warning('本次分摊金额不能超过流水剩余可认领金额');
|
||||
return;
|
||||
}
|
||||
for (const row of this.form.settlements)
|
||||
if (Number(row.allocatedReceiptAmount || 0) > this.remainingAmount(row) + 0.001) {
|
||||
this.$message.warning(`结算单${row.formalSettlementNo}的累计认领金额不能超过结算总金额`);
|
||||
return;
|
||||
}
|
||||
this.submitting = true;
|
||||
try {
|
||||
await api.claim({
|
||||
flowId: this.flowId,
|
||||
attachmentsJson: JSON.stringify(this.form.attachments || []),
|
||||
remark: this.form.remark,
|
||||
settlements: this.form.settlements.map(item => ({
|
||||
settlementId: item.formalSettlementId || item.settlementId,
|
||||
allocatedReceiptAmount: item.allocatedReceiptAmount,
|
||||
})),
|
||||
});
|
||||
this.$message.success('认领成功');
|
||||
this.goBack();
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
},
|
||||
openSettlementDetail(row) {
|
||||
const id = row.formalSettlementId || row.settlementId || row.id;
|
||||
if (id)
|
||||
this.$router.push({
|
||||
path: '/settlement/formal-settlement/form',
|
||||
query: { mode: 'view', id, name: '查看正式结算' },
|
||||
});
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/payment/receipt-flow');
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.receipt-claim-form-page__section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.receipt-claim-form-page__section-title::before {
|
||||
width: 4px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
background: #409eff;
|
||||
content: '';
|
||||
}
|
||||
.receipt-claim-form-page__section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.receipt-claim-form-page__section-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.receipt-claim-form-page__section-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.receipt-claim-form-page__section-heading .receipt-claim-form-page__section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.receipt-claim-form-page__form :deep(.el-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.receipt-claim-form-page__form :deep(.el-select),
|
||||
.receipt-claim-form-page__form :deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
.receipt-claim-form-page__links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.receipt-claim-form-page__remark :deep(.el-form-item__content) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.receipt-claim-form-page__uploader {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.receipt-claim-form-page__actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 16px 0 8px;
|
||||
}
|
||||
.receipt-claim-form-page__dialog-search {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.receipt-claim-form-page__dialog-search .el-input {
|
||||
width: 360px;
|
||||
}
|
||||
.receipt-claim-form-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.receipt-claim-form-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.receipt-claim-form-page__form :deep(.el-col-12) {
|
||||
max-width: 100%;
|
||||
flex: 0 0 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<basic-container class="receipt-flow-page">
|
||||
<section class="receipt-flow-page__search">
|
||||
<el-form :model="query" label-position="right" label-width="160px" @submit.prevent>
|
||||
<div class="receipt-flow-page__search-grid">
|
||||
<el-form-item label="认领通知单">
|
||||
<el-input v-model="query.receiptNoticeNo" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="对方户名">
|
||||
<el-input v-model="query.counterpartyName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="对方开户行">
|
||||
<el-input v-model="query.counterpartyBank" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="对方账号">
|
||||
<el-input v-model="query.counterpartyAccount" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="摘要">
|
||||
<el-input v-model="query.summary" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="认领状态">
|
||||
<el-select v-model="query.claimStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in claimStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="交易时间">
|
||||
<el-date-picker
|
||||
v-model="query.transactionTimeRange"
|
||||
type="datetimerange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
range-separator="~"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="receipt-flow-page__search-actions">
|
||||
<el-button @click="searchExpanded = !searchExpanded">
|
||||
{{ searchExpanded ? '折叠' : '展开' }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</section>
|
||||
|
||||
<section class="receipt-flow-page__table-panel">
|
||||
<div class="receipt-flow-page__toolbar">
|
||||
<div />
|
||||
<el-button
|
||||
v-if="hasPermission('receipt_flow_sync')"
|
||||
type="primary"
|
||||
:loading="syncing"
|
||||
@click="handleSync"
|
||||
>
|
||||
手动同步流水
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="rows" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column
|
||||
v-for="column in columns"
|
||||
:key="column.prop"
|
||||
v-bind="column"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="column.link && canClaim(row)" type="primary" @click="openClaim(row)">
|
||||
{{ displayValue(row[column.prop]) }}
|
||||
</el-link>
|
||||
<span v-else-if="column.link">{{ displayValue(row[column.prop]) }}</span>
|
||||
<el-tag v-else-if="column.status" :type="statusType(row.claimStatus)">
|
||||
{{ displayValue(row[column.prop]) }}
|
||||
</el-tag>
|
||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="canClaim(row)" type="primary" @click="openClaim(row)">认领</el-link>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="receipt-flow-page__pagination">
|
||||
<el-pagination
|
||||
v-model:current-page="page.current"
|
||||
v-model:page-size="page.size"
|
||||
:total="page.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="loadTable"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as api from '@/api/payment/receiptFlow';
|
||||
import { receiptFlowTableColumns } from '@/option/payment/receiptFlow';
|
||||
|
||||
const emptyQuery = () => ({
|
||||
receiptNoticeNo: '',
|
||||
counterpartyName: '',
|
||||
counterpartyBank: '',
|
||||
counterpartyAccount: '',
|
||||
summary: '',
|
||||
claimStatus: '',
|
||||
transactionTimeRange: [],
|
||||
});
|
||||
|
||||
export default {
|
||||
name: 'ReceiptFlow',
|
||||
data() {
|
||||
return {
|
||||
query: emptyQuery(),
|
||||
claimStatusOptions: api.claimStatusOptions,
|
||||
columns: receiptFlowTableColumns,
|
||||
rows: [],
|
||||
loading: false,
|
||||
syncing: false,
|
||||
searchExpanded: false,
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo']),
|
||||
isAdmin() {
|
||||
const authority = this.userInfo?.authority;
|
||||
return Array.isArray(authority)
|
||||
? authority.includes('admin')
|
||||
: String(authority || '').includes('admin');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadTable();
|
||||
},
|
||||
methods: {
|
||||
unwrapData(response) {
|
||||
const body = response?.data || response || {};
|
||||
return body?.data || body;
|
||||
},
|
||||
async loadTable() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const range = this.query.transactionTimeRange || [];
|
||||
const data = this.unwrapData(
|
||||
await api.getList(this.page.current, this.page.size, {
|
||||
...this.query,
|
||||
transactionTimeRange: undefined,
|
||||
transactionStartTime: range[0],
|
||||
transactionEndTime: range[1],
|
||||
})
|
||||
);
|
||||
this.rows = data.records || [];
|
||||
this.page.total = Number(data.total || 0);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
resetSearch() {
|
||||
this.query = emptyQuery();
|
||||
this.handleSearch();
|
||||
},
|
||||
handleSizeChange() {
|
||||
this.page.current = 1;
|
||||
this.loadTable();
|
||||
},
|
||||
async handleSync() {
|
||||
this.syncing = true;
|
||||
try {
|
||||
const syncedCount = Number(this.unwrapData(await api.sync()) || 0);
|
||||
this.$message.success(`流水同步完成,共同步${syncedCount}条`);
|
||||
await this.loadTable();
|
||||
} finally {
|
||||
this.syncing = false;
|
||||
}
|
||||
},
|
||||
openClaim(row) {
|
||||
this.$router.push({
|
||||
path: '/payment/receipt-flow/form',
|
||||
query: { mode: 'add', id: row.id },
|
||||
});
|
||||
},
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission?.[code], false);
|
||||
},
|
||||
canClaim(row) {
|
||||
return this.hasPermission('receipt_flow_claim') && Number(row.remainingAmount || 0) > 0;
|
||||
},
|
||||
statusType(status) {
|
||||
return { claimed: 'success', partial: 'warning', unclaimed: 'info' }[status] || '';
|
||||
},
|
||||
displayValue(value) {
|
||||
return value === null || value === undefined || value === '' ? '-' : value;
|
||||
},
|
||||
formatMoney(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.receipt-flow-page__search {
|
||||
padding: 12px 12px 4px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.receipt-flow-page__search-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 8px 24px;
|
||||
}
|
||||
.receipt-flow-page__search :deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.receipt-flow-page__search :deep(.el-input),
|
||||
.receipt-flow-page__search :deep(.el-select),
|
||||
.receipt-flow-page__search :deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
}
|
||||
.receipt-flow-page__search-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.receipt-flow-page__table-panel {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.receipt-flow-page__toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.receipt-flow-page__pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.receipt-flow-page :deep(.el-table) {
|
||||
--el-table-border-color: #eff1f7;
|
||||
}
|
||||
.receipt-flow-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||
background: #fafafa;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.receipt-flow-page__search-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user