全部账单对接

This commit is contained in:
zengqiyang 2025-04-24 18:29:19 +08:00
parent 9068135f26
commit b573cfdb7f
12 changed files with 3036 additions and 183 deletions

View File

@ -1,5 +1,6 @@
{ {
"dependencies": { "dependencies": {
"element-china-area-data": "^6.1.0",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"mammoth": "^1.9.0", "mammoth": "^1.9.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",

View File

@ -49,7 +49,10 @@
"vue-router": "3.4.9", "vue-router": "3.4.9",
"vuedraggable": "^2.24.3", "vuedraggable": "^2.24.3",
"vuex": "3.6.0", "vuex": "3.6.0",
"xlsx": "^0.18.5" "xlsx": "^0.18.5",
"element-china-area-data": "^6.1.0",
"mammoth": "^1.9.0",
"qrcode.vue": "^3.6.0"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "4.4.6", "@vue/cli-plugin-babel": "4.4.6",

View File

@ -416,7 +416,7 @@ export function uploadBillAttachment(billId, file) {
formData.append('billId', billId) formData.append('billId', billId)
formData.append('file', file) formData.append('file', file)
return request({ return request({
url: '/bill/attachment/upload', url: '/bill/upload-attachment',
method: 'post', method: 'post',
data: formData, data: formData,
headers: { headers: {
@ -425,10 +425,11 @@ export function uploadBillAttachment(billId, file) {
}) })
} }
// 账单附件删除
export function deleteBillAttachment(attachmentId) { export function deleteBillAttachment(attachmentId) {
return request({ return request({
url: `/bill/attachment/${attachmentId}`, url: `/bill/attachment/delete/${attachmentId}`,
method: 'delete' method: 'get'
}) })
} }
@ -446,4 +447,267 @@ export function exportBillList(data) {
data, data,
responseType: 'blob' responseType: 'blob'
}) })
}
// 收支流水相关接口
export function getTransactionList(params) {
return request({
url: '/finance/transaction/page',
method: 'get',
params
})
}
export function getTransactionDetail(id) {
return request({
url: `/finance/transaction/${id}`,
method: 'get'
})
}
export function addTransaction(data) {
return request({
url: '/finance/transaction',
method: 'post',
data
})
}
export function updateTransaction(data) {
return request({
url: '/finance/transaction',
method: 'put',
data
})
}
export function exportTransactionList(params) {
return request({
url: '/finance/transaction/export',
method: 'get',
params,
responseType: 'blob'
})
}
export function getTransactionAttachments(transactionId) {
return request({
url: `/finance/transaction/${transactionId}`,
method: 'get'
})
}
export function uploadTransactionAttachment(transactionId, file) {
const formData = new FormData()
formData.append('transactionId', transactionId)
formData.append('userId', localStorage.getItem('userId') || '')
formData.append('userName', localStorage.getItem('userName') || '')
formData.append('file', file)
return request({
url: '/finance/transaction/attachment',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
export function deleteTransactionAttachment(attachmentId) {
return request({
url: `/finance/transaction/attachment/file/${attachmentId}`,
method: 'delete'
})
}
export function previewAttachment(attachmentId) {
return request({
url: `/finance/transaction/attachment/preview/file/${attachmentId}`,
method: 'get',
responseType: 'blob'
})
}
export function previewAttachmentByFileId(fileId) {
return request({
url: `/finance/transaction/attachment/preview/file/${fileId}`,
method: 'get',
responseType: 'blob'
})
}
export function downloadAttachment(fileId, fileName) {
return request({
url: `/finance/transaction/attachment/download/${fileId}`,
method: 'get',
params: { fileName },
responseType: 'blob'
})
}
export function deleteAttachmentByFileId(fileId) {
return request({
url: `/finance/transaction/attachment/file/${fileId}`,
method: 'delete',
params: {
userId: localStorage.getItem('userId') || ''
}
})
}
export function addTransactionOperation(data) {
return request({
url: '/finance/transaction/operation',
method: 'post',
data
})
}
export function getTransactionOperationLogs(transactionId, query) {
return request({
url: `/finance/transaction/${transactionId}`,
method: 'get'
})
}
// 获取所有收支账户
export function getAllFinanceAccounts() {
return request({
url: '/finance/account/list',
method: 'get'
})
}
// 账单调整相关接口
// 添加账单调整(按金额或按比例)
export function addBillAdjustment(data) {
return request({
url: '/v1/bill-adjustments',
method: 'post',
data
})
}
// 作废账单调整
export function voidBillAdjustment(data) {
return request({
url: '/v1/bill-adjustments/void',
method: 'put',
data
})
}
// 分页查询账单调整记录
export function getBillAdjustmentList(params) {
return request({
url: '/v1/bill-adjustments',
method: 'get',
params
})
}
// 获取账单调整详情
export function getBillAdjustmentDetail(id) {
return request({
url: `/v1/bill-adjustments/${id}`,
method: 'get'
})
}
// 上传账单调整附件
export function uploadBillAdjustmentAttachment(id, file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: `/v1/bill-adjustments/${id}/attachments`,
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
// 删除账单调整附件
export function deleteBillAdjustmentAttachment(attachmentId) {
return request({
url: `/v1/bill-adjustments/attachments/${attachmentId}`,
method: 'delete'
})
}
// 获取账单明细
export function getBillDetailById(detailId) {
return request({
url: `/v1/bill-details/${detailId}`,
method: 'get'
})
}
// 获取账单调整附件列表
export function getBillAdjustmentAttachmentList(adjustmentId) {
return request({
url: `/v1/bill-adjustments/attachments/view/${adjustmentId}`,
method: 'get'
})
}
// 获取账单操作记录
export function getBillOperationRecords(billId, params) {
return request({
url: `/v1/operation-records/list/${billId}`,
method: 'get',
params
})
}
// 账单支付相关接口
// 提交账单支付
export function submitBillPayment(data) {
return request({
url: '/v1/bill-payments',
method: 'post',
data
})
}
// 分页查询支付记录
export function getBillPaymentRecords(params) {
return request({
url: '/v1/bill-payments',
method: 'get',
params
})
}
// 获取支付详情
export function getBillPaymentDetail(id) {
return request({
url: `/v1/bill-payments/${id}`,
method: 'get'
})
}
// 上传支付附件
export function uploadBillPaymentAttachment(id, file) {
const formData = new FormData()
formData.append('file', file)
return request({
url: `/v1/bill-payments/${id}/attachments`,
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
// 删除支付附件
export function deleteBillPaymentAttachment(attachmentId) {
return request({
url: `/v1/bill-payments/attachments/${attachmentId}`,
method: 'delete'
})
} }

View File

@ -0,0 +1,136 @@
<template>
<el-cascader
v-model="selectedRegion"
:options="regionOptions"
:props="{ expandTrigger: 'hover', value: 'value', label: 'label' }"
:placeholder="placeholder"
filterable
clearable
@change="handleChange"
style="width: 100%"
/>
</template>
<script>
import { regionData, pcaTextArr } from 'element-china-area-data'
export default {
name: 'RegionSelector',
props: {
value: {
type: [Array, String],
default: () => []
},
placeholder: {
type: String,
default: '请选择省/市/区'
},
// 使
useTextData: {
type: Boolean,
default: true
}
},
data() {
return {
regionOptions: this.useTextData ? pcaTextArr : regionData,
selectedRegion: []
}
},
watch: {
value: {
handler(val) {
if (val) {
//
if (typeof val === 'string') {
//
const parts = val.split(' ').filter(item => item);
if (parts.length > 0) {
if (this.useTextData) {
// 使
this.selectedRegion = parts;
} else {
// 使
this.convertTextToCode(parts);
}
} else {
this.selectedRegion = [];
}
} else if (Array.isArray(val)) {
// 使
this.selectedRegion = val;
} else {
this.selectedRegion = [];
}
} else {
this.selectedRegion = [];
}
},
immediate: true
}
},
methods: {
//
convertTextToCode(textArray) {
// TextToCode
const findValueByLabel = (options, label, level = 0) => {
for (const option of options) {
if (option.label === label) {
return level === textArray.length - 1 ? option.value :
findValueByLabel(option.children || [], textArray[level + 1], level + 1);
}
}
return null;
};
const value = findValueByLabel(this.regionOptions, textArray[0], 0);
if (value) {
this.selectedRegion = value.split(',');
} else {
this.selectedRegion = [];
}
},
handleChange(value) {
//
if (value && value.length > 0) {
const labels = this.getSelectedLabels(value);
const regionText = labels.join(' ');
this.$emit('input', regionText);
this.$emit('change', regionText, value, labels);
} else {
this.$emit('input', '');
this.$emit('change', '', [], []);
}
},
getSelectedLabels(codes) {
//
const labels = [];
let options = this.regionOptions;
for (let i = 0; i < codes.length; i++) {
const code = codes[i];
const option = options.find(item => item.value === code);
if (option) {
labels.push(option.label);
options = option.children || [];
}
}
return labels;
}
}
}
</script>
<style scoped>
.el-cascader {
width: 100%;
}
</style>

View File

@ -30,6 +30,12 @@ export default {
component: () => import('@/views/finance/billList/index.vue'), component: () => import('@/views/finance/billList/index.vue'),
name: 'BillList', name: 'BillList',
meta: { title: '所有账单', icon: 'el-icon-notebook-2' } meta: { title: '所有账单', icon: 'el-icon-notebook-2' }
},
{
path: 'transaction',
component: () => import('@/views/finance/transaction/index.vue'),
name: 'Transaction',
meta: { title: '收支流水', icon: 'el-icon-money' }
} }
] ]
} }

View File

@ -5,8 +5,8 @@
<el-form ref="chargeModeForm" :model="billForm" :rules="chargeModeRules" label-width="120px"> <el-form ref="chargeModeForm" :model="billForm" :rules="chargeModeRules" label-width="120px">
<el-form-item label="含税规则" prop="taxInclusiveRule" required> <el-form-item label="含税规则" prop="taxInclusiveRule" required>
<el-radio-group v-model="billForm.taxInclusiveRule"> <el-radio-group v-model="billForm.taxInclusiveRule">
<el-radio label="含税">含税</el-radio> <el-radio label="1" value="1">含税</el-radio>
<el-radio label="不含税">不含税</el-radio> <el-radio label="2" value="2">不含税</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="税率(%)" prop="taxRate" required> <el-form-item label="税率(%)" prop="taxRate" required>
@ -14,18 +14,18 @@
</el-form-item> </el-form-item>
<el-form-item label="特殊账单类型" prop="specialBillType" required> <el-form-item label="特殊账单类型" prop="specialBillType" required>
<el-radio-group v-model="billForm.specialBillType"> <el-radio-group v-model="billForm.specialBillType">
<el-radio label="正常">正常</el-radio> <el-radio label="1" value="1">正常</el-radio>
<el-radio label="罚金">罚金</el-radio> <el-radio label="2" value="2">罚金</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="滞纳金起算天数" prop="lateFeeStartDays"> <el-form-item label="滞纳金起算天数" prop="ovdueStartDays">
<el-input-number v-model="billForm.lateFeeStartDays" :min="0" :precision="0" style="width: 200px"></el-input-number> <el-input-number v-model="billForm.ovdueStartDays" :min="0" :precision="0" style="width: 200px"></el-input-number>
</el-form-item> </el-form-item>
<el-form-item label="滞纳金比例(%/天)" prop="lateFeeRate"> <el-form-item label="滞纳金比例(%/天)" prop="ovdueIntRate">
<el-input-number v-model="billForm.lateFeeRate" :min="0" :max="100" :precision="10" style="width: 200px"></el-input-number> <el-input-number v-model="billForm.ovdueIntRate" :min="0" :max="100" :precision="10" style="width: 200px"></el-input-number>
</el-form-item> </el-form-item>
<el-form-item label="滞纳金上限(%)" prop="lateFeeLimit"> <el-form-item label="滞纳金上限(%)" prop="ovdueLimitRate">
<el-input-number v-model="billForm.lateFeeLimit" :min="0" :max="100" :precision="10" style="width: 200px"></el-input-number> <el-input-number v-model="billForm.ovdueLimitRate" :min="0" :max="100" :precision="10" style="width: 200px"></el-input-number>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-tab-pane> </el-tab-pane>
@ -64,8 +64,8 @@
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="付款方" prop="payeeId" required> <el-form-item label="付款方" prop="paysdId" required>
<el-select v-model="billForm.payeeId" placeholder="请选择付款方" filterable remote clearable <el-select v-model="billForm.paysdId" placeholder="请选择付款方" filterable remote clearable
:remote-method="searchPayees" @change="handlePayeeChange" style="width: 400px"> :remote-method="searchPayees" @change="handlePayeeChange" style="width: 400px">
<el-option v-for="item in payeeOptions" :key="item.id" <el-option v-for="item in payeeOptions" :key="item.id"
:label="item.name" :value="item.id"> :label="item.name" :value="item.id">
@ -75,12 +75,12 @@
<el-form-item label="币种" prop="currCode" required> <el-form-item label="币种" prop="currCode" required>
<el-input v-model="billForm.currCode" disabled style="width: 400px"></el-input> <el-input v-model="billForm.currCode" disabled style="width: 400px"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="应收金额" prop="receivableAmount" required> <el-form-item label="应收金额" prop="accigRcvAmt" required>
<el-input-number v-model="billForm.receivableAmount" :min="0" :precision="2" style="width: 400px"></el-input-number> <el-input-number v-model="billForm.accigRcvAmt" :min="0" :precision="2" style="width: 400px"></el-input-number>
</el-form-item> </el-form-item>
<el-form-item label="应收日期" prop="receivableDate" required> <el-form-item label="应收日期" prop="pybDt" required>
<el-date-picker <el-date-picker
v-model="billForm.receivableDate" v-model="billForm.pybDt"
type="date" type="date"
placeholder="选择日期" placeholder="选择日期"
value-format="yyyy-MM-dd" value-format="yyyy-MM-dd"
@ -139,18 +139,39 @@
<el-table-column label="操作" width="200" align="center"> <el-table-column label="操作" width="200" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="mini" type="text" @click="previewFile(scope.row)">预览</el-button> <el-button size="mini" type="text" @click="previewFile(scope.row)">预览</el-button>
<el-button size="mini" type="text" @click="downloadFile(scope.row)">下载</el-button>
<el-button size="mini" type="text" class="delete-btn" @click="deleteFile(scope.$index)">删除</el-button> <el-button size="mini" type="text" class="delete-btn" @click="deleteFile(scope.$index)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<!-- 文件预览对话框 -->
<el-dialog title="附件预览" :visible.sync="previewDialogVisible" width="800px" append-to-body>
<div class="preview-container" v-loading="previewLoading">
<div v-if="previewUrl && isPdf" class="preview-iframe">
<iframe :src="previewUrl" width="100%" height="500px" frameborder="0"></iframe>
</div>
<div v-else-if="previewUrl && isImage" class="preview-image">
<img :src="previewUrl" style="max-width: 100%;" />
</div>
<div v-else-if="!previewLoading" class="preview-error">
<i class="el-icon-warning"></i>
<p>预览加载失败请尝试下载后查看</p>
<div class="preview-actions">
<el-button type="primary" @click="downloadCurrentPreview">下载文件</el-button>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="previewDialogVisible = false"> </el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import { getFeeTypeTree } from '@/api/finance' import { getFeeTypeTree, uploadBillAttachment, addBill } from '@/api/finance'
export default { export default {
name: 'AddBill', name: 'AddBill',
@ -167,30 +188,28 @@ export default {
billForm: { billForm: {
contractId: '', contractId: '',
contractNumber: '', contractNumber: '',
payeeId: '', paysdId: '',
payeeName: '', paysdName: '',
payeeContact: '', payeeContact: '',
feeCategoryId: '', feeCategoryId: '',
feeTypeId: '', feeTypeId: '',
feeTypeName: '', feTpName: '',
currCode: '156', // currCode: '156', //
billingStartDate: '', chggBgnDt: '',
billingEndDate: '', chggEndDt: '',
receivableAmount: 0, accigRcvAmt: 0,
price: 0, taxInclusiveRule: '1',
priceU: '元/月',
taxInclusiveRule: '含税',
taxRate: 0, taxRate: 0,
receivableDate: '', pybDt: '',
specialBillType: '正常', specialBillType: '1',
lateFeeStartDays: 0, ovdueStartDays: 0,
lateFeeRate: 0, ovdueIntRate: 0,
lateFeeLimit: 0, ovdueLimitRate: 0,
companyId: '', companyId: '',
companyName: '', corNm: '',
accountId: '', accountId: '',
billRemark: '', billRemark: '',
roomInfoList: [] roomIds: []
}, },
// //
chargeModeRules: { chargeModeRules: {
@ -205,7 +224,7 @@ export default {
] ]
}, },
billInfoRules: { billInfoRules: {
payeeId: [ paysdId: [
{ required: true, message: '请选择付款方', trigger: 'change' } { required: true, message: '请选择付款方', trigger: 'change' }
], ],
companyId: [ companyId: [
@ -214,10 +233,10 @@ export default {
accountId: [ accountId: [
{ required: true, message: '请选择收支账户', trigger: 'change' } { required: true, message: '请选择收支账户', trigger: 'change' }
], ],
receivableAmount: [ accigRcvAmt: [
{ required: true, message: '请输入应收金额', trigger: 'blur' } { required: true, message: '请输入应收金额', trigger: 'blur' }
], ],
receivableDate: [ pybDt: [
{ required: true, message: '请选择应收日期', trigger: 'change' } { required: true, message: '请选择应收日期', trigger: 'change' }
] ]
}, },
@ -244,7 +263,16 @@ export default {
selectedRoomIds: [], selectedRoomIds: [],
// //
fileList: [], fileList: [],
attachmentLoading: false attachmentLoading: false,
//
previewDialogVisible: false,
previewLoading: false,
previewUrl: '',
previewBlob: null,
currentPreviewName: '',
isPdf: false,
isImage: false,
} }
}, },
created() { created() {
@ -280,10 +308,10 @@ export default {
children = category.financeFeeTypes.map(feeType => { children = category.financeFeeTypes.map(feeType => {
return { return {
id: feeType.id || Math.random().toString(36).substr(2, 9), id: feeType.id || Math.random().toString(36).substr(2, 9),
label: feeType.feeTypeName || '未命名费用', label: feeType.feTpName || '未命名费用',
categoryId: categoryId, categoryId: categoryId,
// 便使 // 便使
feeTypeName: feeType.feeTypeName || '未命名费用', feTpName: feeType.feTpName || '未命名费用',
// //
children: null children: null
} }
@ -302,7 +330,7 @@ export default {
getCompanyOptions() { getCompanyOptions() {
// API // API
this.companyOptions = [ this.companyOptions = [
{ id: 'C001', name: '智慧园区物业管理有限公司' } { id: 123, name: '智慧园区物业管理有限公司' }
] ]
}, },
// //
@ -318,8 +346,8 @@ export default {
if (query) { if (query) {
// API // API
this.contractOptions = [ this.contractOptions = [
{ contractId: 'CT001', contractNumber: 'HT2023001', customerName: '张三' }, { contractId: 11, contractNumber: 'HT2023001', customerName: '张三' },
{ contractId: 'CT002', contractNumber: 'HT2023002', customerName: '李四' } { contractId: 22, contractNumber: 'HT2023002', customerName: '李四' }
] ]
} else { } else {
this.contractOptions = [] this.contractOptions = []
@ -338,19 +366,21 @@ export default {
} }
}, },
// //
handlePayeeChange(payeeId) { handlePayeeChange(paysdId) {
const payee = this.payeeOptions.find(item => item.id === payeeId) const payee = this.payeeOptions.find(item => item.id === paysdId)
if (payee) { if (payee) {
this.billForm.payeeName = payee.name this.billForm.paysdName = payee.name
this.billForm.payeeContact = payee.contact this.billForm.payeeContact = payee.contact
// //
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.billInfoForm.validateField('payeeId') this.$refs.billInfoForm.validateField('paysdId')
}) })
} }
}, },
// //
handleFeeTypeChange(value) { handleFeeTypeChange(value) {
console.log(value);
if (value) { if (value) {
// //
let selectedFeeType = null; let selectedFeeType = null;
@ -370,7 +400,7 @@ export default {
} }
// //
if (category.children && category.children.length > 0) { if (category.children && category.children.length > 0) {
const found = category.children.find(item => item.id === value); const found = category.children.find(item => item.id === value[1]);
if (found) { if (found) {
selectedCategory = category; selectedCategory = category;
selectedFeeType = { selectedFeeType = {
@ -389,25 +419,25 @@ export default {
if (selectedFeeType.type === '分类') { if (selectedFeeType.type === '分类') {
this.billForm.feeCategoryId = selectedFeeType.id; this.billForm.feeCategoryId = selectedFeeType.id;
this.billForm.feeTypeId = ''; this.billForm.feeTypeId = '';
this.billForm.feeTypeName = selectedFeeType.name; this.billForm.feTpName = selectedFeeType.name;
} else { } else {
this.billForm.feeCategoryId = selectedCategory.id; this.billForm.feeCategoryId = selectedCategory.id;
this.billForm.feeTypeId = selectedFeeType.id; this.billForm.feeTypeId = selectedFeeType.id;
this.billForm.feeTypeName = selectedFeeType.name; this.billForm.feTpName = selectedFeeType.name;
} }
} }
} else { } else {
// //
this.billForm.feeCategoryId = ''; this.billForm.feeCategoryId = '';
this.billForm.feeTypeId = ''; this.billForm.feeTypeId = '';
this.billForm.feeTypeName = ''; this.billForm.feTpName = '';
} }
}, },
// //
handleCompanyChange(companyId) { handleCompanyChange(companyId) {
const company = this.companyOptions.find(item => item.id === companyId) const company = this.companyOptions.find(item => item.id === companyId)
if (company) { if (company) {
this.billForm.companyName = company.name this.billForm.corNm = company.name
// //
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.billInfoForm.validateField('companyId') this.$refs.billInfoForm.validateField('companyId')
@ -420,11 +450,11 @@ export default {
// //
handleBillingPeriodChange(val) { handleBillingPeriodChange(val) {
if (val && val.length === 2) { if (val && val.length === 2) {
this.billForm.billingStartDate = val[0] this.billForm.chggBgnDt = val[0]
this.billForm.billingEndDate = val[1] this.billForm.chggEndDt = val[1]
} else { } else {
this.billForm.billingStartDate = '' this.billForm.chggBgnDt = ''
this.billForm.billingEndDate = '' this.billForm.chggEndDt = ''
} }
}, },
@ -525,9 +555,6 @@ export default {
// ID // ID
this.selectedRoomIds = roomNodes.map(node => node.id) this.selectedRoomIds = roomNodes.map(node => node.id)
//
this.billForm.roomInfoList = []
// Map // Map
const projectMap = new Map() const projectMap = new Map()
@ -553,20 +580,63 @@ export default {
// //
if (roomInfo && floorNode && buildingNode && projectNode) { if (roomInfo && floorNode && buildingNode && projectNode) {
// // Map
this.billForm.roomInfoList.push({ if (!projectMap.has(projectNode.id)) {
roomId: roomNode.id, projectMap.set(projectNode.id, {
id: projectNode.id,
projectName: projectNode.label,
projectType: '产业园区', // projectType使projectNode
buildings: []
})
}
//
const project = projectMap.get(projectNode.id)
//
let building = project.buildings.find(b => b.id === buildingNode.id)
if (!building) {
building = {
id: buildingNode.id,
buildingName: buildingNode.label,
buildingCode: '', // buildingCode使buildingNode
floors: []
}
project.buildings.push(building)
}
//
let floor = building.floors.find(f => f.id === floorNode.id)
if (!floor) {
floor = {
id: floorNode.id,
floorName: floorNode.label,
floorNumber: 0, // floorNumber使floorNode
rooms: []
}
building.floors.push(floor)
}
//
const room = {
id: roomInfo.id,
roomNumber: roomInfo.roomNumber || roomNode.label.split('')[0], roomNumber: roomInfo.roomNumber || roomNode.label.split('')[0],
buildingId: buildingNode.id, roomType: roomInfo.roomType || '',
buildingName: buildingNode.label, roomStatus: roomInfo.roomStatus || '1',
floorId: floorNode.id, buildingArea: roomInfo.buildingArea || 0,
floorName: floorNode.label, rentalArea: roomInfo.rentalArea || 0
projectId: projectNode.id, }
projectName: projectNode.label,
rentArea: roomInfo.rentalArea || 0 //
}) const roomExists = floor.rooms.some(r => r.id === room.id)
if (!roomExists) {
floor.rooms.push(room)
}
} }
}) })
// Map
this.billForm.roomIds = Array.from(projectMap.values())
}, },
// //
beforeUpload(file) { beforeUpload(file) {
@ -586,32 +656,87 @@ export default {
// //
uploadFile(options) { uploadFile(options) {
this.attachmentLoading = true this.attachmentLoading = true
const file = options.file
// API // URL
setTimeout(() => { const tempUrl = URL.createObjectURL(file)
this.fileList.push({ const fileName = file.name
name: options.file.name,
url: URL.createObjectURL(options.file), //
operatorName: '当前用户', const currentUser = this.$store.getters.name || '当前用户'
operateTime: new Date().toLocaleString()
}) this.fileList.push({
name: fileName,
this.attachmentLoading = false file: file,
this.$message.success('上传成功') url: tempUrl,
}, 1000) operatorName: currentUser,
operateTime: new Date().toLocaleString()
})
this.attachmentLoading = false
this.$message.success('上传成功')
console.log(this.fileList);
}, },
// //
previewFile(file) { previewFile(file) {
window.open(file.url) this.previewLoading = true
this.previewDialogVisible = true
this.currentPreviewName = file.name || '附件'
this.previewUrl = ''
this.isPdf = false
this.isImage = false
//
const fileExt = file.name ? file.name.split('.').pop().toLowerCase() : ''
try {
//
const blob = file.file ? file.file : new Blob()
this.previewBlob = blob
//
if (fileExt === 'pdf') {
this.isPdf = true
} else if (['jpg', 'jpeg', 'png', 'gif'].includes(fileExt)) {
this.isImage = true
}
// URL
this.previewUrl = file.url || URL.createObjectURL(blob)
this.previewLoading = false
} catch (error) {
console.error('预览失败', error)
this.$message.error('获取预览数据失败')
this.previewLoading = false
}
}, },
//
downloadCurrentPreview() {
if (!this.previewUrl) {
this.$message.error('没有可下载的文件')
return
}
const link = document.createElement('a')
link.href = this.previewUrl
link.setAttribute('download', this.currentPreviewName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
},
// //
downloadFile(file) { downloadFile(file) {
// API
const link = document.createElement('a') const link = document.createElement('a')
link.href = file.url link.href = file.url
link.download = file.name link.download = file.name
link.click() link.click()
}, },
// //
deleteFile(index) { deleteFile(index) {
this.$confirm('确认删除该附件?', '提示', { this.$confirm('确认删除该附件?', '提示', {
@ -631,8 +756,8 @@ export default {
} }
if (this.formExtension.billingPeriod && this.formExtension.billingPeriod.length === 2) { if (this.formExtension.billingPeriod && this.formExtension.billingPeriod.length === 2) {
this.billForm.billingStartDate = this.formExtension.billingPeriod[0] this.billForm.chggBgnDt = this.formExtension.billingPeriod[0]
this.billForm.billingEndDate = this.formExtension.billingPeriod[1] this.billForm.chggEndDt = this.formExtension.billingPeriod[1]
} }
return this.billForm return this.billForm
@ -665,7 +790,7 @@ export default {
} }
// //
if (this.billForm.roomInfoList.length === 0) { if (this.billForm.roomIds.length === 0) {
this.$message.warning('请至少选择一个房源') this.$message.warning('请至少选择一个房源')
this.activeTab = 'roomInfo' this.activeTab = 'roomInfo'
resolve(false) resolve(false)
@ -698,30 +823,28 @@ export default {
this.billForm = { this.billForm = {
contractId: '', contractId: '',
contractNumber: '', contractNumber: '',
payeeId: '', paysdId: '',
payeeName: '', paysdName: '',
payeeContact: '', payeeContact: '',
feeCategoryId: '', feeCategoryId: '',
feeTypeId: '', feeTypeId: '',
feeTypeName: '', feTpName: '',
currCode: '156', // currCode: '156', //
billingStartDate: '', chggBgnDt: '',
billingEndDate: '', chggEndDt: '',
receivableAmount: 0, accigRcvAmt: 0,
price: 0, taxInclusiveRule: '1',
priceU: '元/月',
taxInclusiveRule: '含税',
taxRate: 0, taxRate: 0,
receivableDate: '', pybDt: '',
specialBillType: '正常', specialBillType: '1',
lateFeeStartDays: 0, ovdueStartDays: 0,
lateFeeRate: 0, ovdueIntRate: 0,
lateFeeLimit: 0, ovdueLimitRate: 0,
companyId: '', companyId: '',
companyName: '', corNm: '',
accountId: '', accountId: '',
billRemark: '', billRemark: '',
roomInfoList: [] roomIds: []
} }
// //
@ -738,7 +861,62 @@ export default {
// //
this.fileList = [] this.fileList = []
} },
//
submitForm() {
this.validateForm().then(valid => {
if (valid) {
const formData = this.getFormData()
this.loading = true
addBill(formData)
.then(response => {
if (response.code === '0000000000000000') {
this.$message.success('添加账单成功')
//
if (this.fileList.length > 0) {
this.uploadAttachments(response.data)
} else {
this.$emit('success')
this.resetForm()
}
} else {
this.$message.error(response.message || '添加账单失败')
this.loading = false
}
})
.catch(error => {
console.error('添加账单失败', error)
this.$message.error('添加账单失败,请检查表单数据')
this.loading = false
})
}
})
},
//
uploadAttachments(billId) {
const uploadPromises = this.fileList.map(fileItem => {
return uploadBillAttachment(billId, fileItem.file)
})
Promise.all(uploadPromises)
.then(() => {
this.$message.success('附件上传成功')
this.$emit('success')
this.resetForm()
this.loading = false
})
.catch(error => {
console.error('附件上传失败', error)
this.$message.warning('账单创建成功,但部分附件上传失败')
this.$emit('success')
this.resetForm()
this.loading = false
})
},
} }
} }
</script> </script>
@ -755,4 +933,45 @@ export default {
color: #F56C6C; color: #F56C6C;
} }
} }
.preview-container {
min-height: 500px;
max-height: 700px;
overflow: auto;
.preview-iframe {
width: 100%;
height: 500px;
border: none;
}
.preview-image {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
}
.preview-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
color: #909399;
i {
font-size: 48px;
margin-bottom: 20px;
}
p {
font-size: 16px;
}
.preview-actions {
margin-top: 20px;
}
}
}
</style> </style>

File diff suppressed because it is too large Load Diff

View File

@ -2,17 +2,17 @@
<div class="app-container"> <div class="app-container">
<!-- 搜索表单 --> <!-- 搜索表单 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" class="search-form"> <el-form :model="queryParams" ref="queryForm" :inline="true" class="search-form">
<el-form-item label="合同编号" prop="contractNumber"> <el-form-item label="合同编号" prop="contractNo">
<el-input v-model="queryParams.contractNumber" placeholder="请输入合同编号" clearable style="width: 220px" /> <el-input v-model="queryParams.contractNo" placeholder="请输入合同编号" clearable style="width: 220px" />
</el-form-item> </el-form-item>
<el-form-item label="房号" prop="roomNumber"> <el-form-item label="房号" prop="roomNumber">
<el-input v-model="queryParams.roomNumber" placeholder="请输入房号" clearable style="width: 220px" /> <el-input v-model="queryParams.roomNumber" placeholder="请输入房号" clearable style="width: 220px" />
</el-form-item> </el-form-item>
<el-form-item label="对方名称" prop="payeeName"> <el-form-item label="对方名称" prop="paysdName">
<el-input v-model="queryParams.payeeName" placeholder="请输入对方名称" clearable style="width: 220px" /> <el-input v-model="queryParams.paysdName" placeholder="请输入对方名称" clearable style="width: 220px" />
</el-form-item> </el-form-item>
<el-form-item label="账单编号" prop="billNumber"> <el-form-item label="账单编号" prop="totBillNo">
<el-input v-model="queryParams.billNumber" placeholder="请输入账单编号" clearable style="width: 220px" /> <el-input v-model="queryParams.totBillNo" placeholder="请输入账单编号" clearable style="width: 220px" />
</el-form-item> </el-form-item>
<el-form-item label="账单状态" prop="billStatus"> <el-form-item label="账单状态" prop="billStatus">
<el-select v-model="queryParams.billStatus" placeholder="请选择账单状态" clearable multiple style="width: 220px"> <el-select v-model="queryParams.billStatus" placeholder="请选择账单状态" clearable multiple style="width: 220px">
@ -20,7 +20,7 @@
<el-option label="关闭" value="0" /> <el-option label="关闭" value="0" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="应收时间" prop="receivableDate"> <el-form-item label="应收时间" prop="pybDt">
<el-date-picker <el-date-picker
v-model="dateRange" v-model="dateRange"
type="daterange" type="daterange"
@ -77,11 +77,11 @@
<!-- 表格区域 --> <!-- 表格区域 -->
<el-table v-loading="loading" :data="billList" border> <el-table v-loading="loading" :data="billList" border>
<el-table-column label="对方名称" prop="payeeName" min-width="120" show-overflow-tooltip /> <el-table-column label="对方名称" prop="paysdName" min-width="120" show-overflow-tooltip />
<el-table-column label="项目楼宇名称" prop="projectName" min-width="120" show-overflow-tooltip /> <el-table-column label="项目楼宇名称" prop="projectName" min-width="120" show-overflow-tooltip />
<el-table-column label="房号" prop="roomNumber" min-width="100" show-overflow-tooltip /> <el-table-column label="房号" prop="roomNumber" min-width="100" show-overflow-tooltip />
<el-table-column label="账单编号" prop="billNumber" min-width="150" show-overflow-tooltip /> <el-table-column label="账单编号" prop="totBillNo" min-width="150" show-overflow-tooltip />
<el-table-column label="合同编号" prop="contractNumber" min-width="150" show-overflow-tooltip /> <el-table-column label="合同编号" prop="contractNo" min-width="150" show-overflow-tooltip />
<el-table-column label="结清状态" min-width="100" align="center"> <el-table-column label="结清状态" min-width="100" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag :type="getClearStatusType(scope.row.clearStatus)"> <el-tag :type="getClearStatusType(scope.row.clearStatus)">
@ -89,25 +89,25 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="费用类型" prop="feeTypeName" min-width="120" show-overflow-tooltip /> <el-table-column label="费用类型" prop="feTpName" min-width="120" show-overflow-tooltip />
<el-table-column label="账单金额" prop="billAmount" min-width="100" align="right"> <el-table-column label="账单金额" prop="accblAmt" min-width="100" align="right">
<template slot-scope="scope"> <template slot-scope="scope">
{{ scope.row.billAmount ? scope.row.billAmount.toFixed(2) : '0.00' }} {{ scope.row.accblAmt ? scope.row.accblAmt.toFixed(2) : '0.00' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="应收金额" prop="receivableAmount" min-width="100" align="right"> <el-table-column label="应收金额" prop="accigRcvAmt" min-width="100" align="right">
<template slot-scope="scope"> <template slot-scope="scope">
{{ scope.row.receivableAmount ? scope.row.receivableAmount.toFixed(2) : '0.00' }} {{ scope.row.accigRcvAmt ? scope.row.accigRcvAmt.toFixed(2) : '0.00' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="实收金额" prop="receivedAmount" min-width="100" align="right"> <el-table-column label="实收金额" prop="atmRecvAmt" min-width="100" align="right">
<template slot-scope="scope"> <template slot-scope="scope">
{{ scope.row.receivedAmount ? scope.row.receivedAmount.toFixed(2) : '0.00' }} {{ scope.row.atmRecvAmt ? scope.row.atmRecvAmt.toFixed(2) : '0.00' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="开始日期" prop="billingStartDate" min-width="120" align="center" /> <el-table-column label="开始日期" prop="chggBgnDt" min-width="120" align="center" />
<el-table-column label="结束日期" prop="billingEndDate" min-width="120" align="center" /> <el-table-column label="结束日期" prop="chggEndDt" min-width="120" align="center" />
<el-table-column label="应收日期" prop="receivableDate" min-width="120" align="center" /> <el-table-column label="应收日期" prop="pybDt" min-width="120" align="center" />
<el-table-column label="操作" width="150" align="center" fixed="right"> <el-table-column label="操作" width="150" align="center" fixed="right">
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleViewDetail(scope.row)">查看</el-button> <el-button size="mini" type="text" icon="el-icon-view" @click="handleViewDetail(scope.row)">查看</el-button>
@ -171,10 +171,10 @@ export default {
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
contractNumber: '', contractNo: '',
roomNumber: '', roomNumber: '',
payeeName: '', paysdName: '',
billNumber: '', totBillNo: '',
billStatus: [], billStatus: [],
receivableDateStart: '', receivableDateStart: '',
receivableDateEnd: '', receivableDateEnd: '',
@ -217,7 +217,7 @@ export default {
} }
getBillList(queryParams).then(response => { getBillList(queryParams).then(response => {
this.billList = response.data.data || [] this.billList = response.data.list || []
this.total = response.data.total this.total = response.data.total
this.loading = false this.loading = false
}).catch(() => { }).catch(() => {
@ -250,10 +250,10 @@ export default {
children = category.financeFeeTypes.map(feeType => { children = category.financeFeeTypes.map(feeType => {
return { return {
id: feeType.id || Math.random().toString(36).substr(2, 9), id: feeType.id || Math.random().toString(36).substr(2, 9),
label: feeType.feeTypeName || '未命名费用', label: feeType.feTpName || '未命名费用',
categoryId: categoryId, categoryId: categoryId,
// 便使 // 便使
feeTypeName: feeType.feeTypeName || '未命名费用', feTpName: feeType.feTpName || '未命名费用',
// //
children: null children: null
} }
@ -353,12 +353,28 @@ export default {
this.$refs.addBillForm.validateForm().then(valid => { this.$refs.addBillForm.validateForm().then(valid => {
if (valid) { if (valid) {
const billData = this.$refs.addBillForm.getFormData() const billData = this.$refs.addBillForm.getFormData()
billData.billType = 1
billData.billSource = 3
addBill(billData).then(response => { addBill(billData).then(response => {
this.$message.success('添加账单成功') if (response.code === '0000000000000000') {
this.dialogVisible = false this.$message.success('添加账单成功')
this.getList()
//
const fileList = this.$refs.addBillForm.fileList
if (fileList && fileList.length > 0) {
this.$refs.addBillForm.uploadAttachments(response.data)
//
} else {
this.dialogVisible = false
this.getList()
}
} else {
this.$message.error(response.message || '添加账单失败')
}
}).catch(() => { }).catch(() => {
// //
this.$message.error('添加账单失败')
}) })
} }
}) })

View File

@ -0,0 +1,520 @@
<template>
<div class="transaction-detail-container" v-loading="loading">
<!-- 详情主体 -->
<el-tabs v-model="activeTab">
<!-- 总体信息 -->
<el-tab-pane label="总体信息" name="overall">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>总体信息</span>
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="对方单位名称">{{ detailData.totalInfo && detailData.totalInfo.unitName || '-' }}</el-descriptions-item>
<el-descriptions-item label="总金额">
<span class="amount">{{ formatAmount(detailData.totalInfo && detailData.totalInfo.totalAmount) }}</span>
</el-descriptions-item>
<el-descriptions-item label="已匹配金额">
<span class="amount matched">{{ formatAmount(detailData.totalInfo && detailData.totalInfo.matchedAmount) }}</span>
</el-descriptions-item>
<el-descriptions-item label="未匹配金额">
<span class="amount unmatched">{{ formatAmount(detailData.totalInfo && detailData.totalInfo.unmatchedAmount) }}</span>
</el-descriptions-item>
</el-descriptions>
</el-card>
</el-tab-pane>
<!-- 流水信息 -->
<el-tab-pane label="流水信息" name="transaction">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>流水信息</span>
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="借贷标">
<el-tag :type="(detailData.transaction && detailData.transaction.dcFlagCode === '0') ? 'danger' : 'success'">
{{ (detailData.transaction && detailData.transaction.dcFlagCode === '0') ? '借(支出)' : '贷(收入)' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="发生额">
<span class="amount">{{ formatAmount(detailData.transaction && detailData.transaction.occuAmt) }}</span>
</el-descriptions-item>
<el-descriptions-item label="入账日期">{{ detailData.transaction && detailData.transaction.inaccDate || '-' }}</el-descriptions-item>
<el-descriptions-item label="对方单位名称">{{ detailData.transaction && detailData.transaction.unitName || '-' }}</el-descriptions-item>
<el-descriptions-item label="支付方式">{{ getPaymentModeName(detailData.transaction && detailData.transaction.payModeName) }}</el-descriptions-item>
<el-descriptions-item label="对方账号">{{ detailData.transaction && detailData.transaction.opsAccname || '-' }}</el-descriptions-item>
<el-descriptions-item label="凭证号">{{ detailData.transaction && detailData.transaction.txVchrNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="收据编号">{{ detailData.transaction && detailData.transaction.receiptNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系人">{{ detailData.transaction && detailData.transaction.conterName || '-' }}</el-descriptions-item>
<el-descriptions-item label="流水账户">{{ detailData.transaction && detailData.transaction.openaccBankName || '-' }}</el-descriptions-item>
<el-descriptions-item label="摘要">{{ detailData.transaction && detailData.transaction.summ || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ detailData.transaction && detailData.transaction.remark || '-' }}</el-descriptions-item>
</el-descriptions>
</el-card>
</el-tab-pane>
<!-- 匹配账单新增的独立标签页 -->
<el-tab-pane label="匹配账单" name="matchBills">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>匹配账单</span>
</div>
<el-table :data="detailData.relatedBills || []" border style="width: 100%">
<el-table-column prop="unitName" label="对方名称" min-width="120" show-overflow-tooltip />
<el-table-column prop="feeTypeName" label="费用类型" min-width="120" show-overflow-tooltip />
<el-table-column label="费用周期" min-width="200">
<template slot-scope="scope">
{{ scope.row.billingStartDate || '-' }} {{ scope.row.billingEndDate || '-' }}
</template>
</el-table-column>
<el-table-column prop="receivableAmount" label="应收金额" min-width="120" align="right">
<template slot-scope="scope">
{{ formatAmount(scope.row.receivableAmount) }}
</template>
</el-table-column>
</el-table>
<div v-if="!detailData.relatedBills || detailData.relatedBills.length === 0" class="empty-data">
暂无匹配账单
</div>
</el-card>
</el-tab-pane>
<!-- 附件信息 -->
<el-tab-pane label="附件信息" name="attachment">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>附件信息</span>
<el-button style="float: right;" type="primary" size="small" @click="handleAddAttachment">
<i class="el-icon-plus"></i> 添加附件
</el-button>
</div>
<el-table :data="detailData.attachments || []" border style="width: 100%">
<el-table-column prop="fileName" label="文件名" min-width="250" show-overflow-tooltip />
<el-table-column prop="createUserName" label="操作人" width="150" />
<el-table-column prop="createTime" label="操作时间" width="180" />
<el-table-column label="操作" width="200" align="center">
<template slot-scope="scope">
<el-button size="mini" type="text" @click="previewFile(scope.row)">预览</el-button>
<el-button size="mini" type="text" @click="downloadFile(scope.row)">下载</el-button>
<el-button size="mini" type="text" class="delete-btn" @click="deleteFile(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="!detailData.attachments || detailData.attachments.length === 0" class="empty-data">
暂无附件
</div>
</el-card>
<!-- 文件上传对话框 -->
<el-dialog title="上传附件" :visible.sync="uploadDialogVisible" width="500px" append-to-body>
<el-upload
class="upload-area"
action="#"
:http-request="uploadFile"
:file-list="uploadFileList"
:before-upload="beforeUpload"
multiple
:limit="10">
<el-button type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png/pdf文件且不超过5MB</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button @click="uploadDialogVisible = false"> </el-button>
<el-button type="primary" @click="confirmUpload"> </el-button>
</div>
</el-dialog>
</el-tab-pane>
<!-- 操作记录 -->
<el-tab-pane label="操作记录" name="logs">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>操作记录</span>
</div>
<el-table :data="detailData.operationRecords || []" border style="width: 100%" v-loading="logsLoading">
<el-table-column prop="oprPersonNm" label="操作人" width="150" />
<el-table-column prop="oprContent" label="操作内容" min-width="250" show-overflow-tooltip />
<el-table-column prop="createTime" label="操作时间" width="180" />
</el-table>
<div v-if="!detailData.operationRecords || detailData.operationRecords.length === 0" class="empty-data">
暂无操作记录
</div>
</el-card>
</el-tab-pane>
</el-tabs>
<!-- 文件预览对话框 -->
<el-dialog title="附件预览" :visible.sync="previewDialogVisible" width="800px" append-to-body>
<div class="preview-container" v-loading="previewLoading">
<div v-if="previewUrl && isPdf" class="preview-iframe">
<iframe :src="previewUrl" width="100%" height="500px" frameborder="0"></iframe>
</div>
<div v-else-if="previewUrl && isImage" class="preview-image">
<img :src="previewUrl" style="max-width: 100%;" />
</div>
<div v-else-if="!previewLoading" class="preview-error">
<i class="el-icon-warning"></i>
<p>预览加载失败请尝试下载后查看</p>
<div class="preview-actions">
<el-button type="primary" @click="downloadCurrentPreview">下载文件</el-button>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="previewDialogVisible = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
getTransactionDetail,
uploadTransactionAttachment,
deleteTransactionAttachment,
getTransactionOperationLogs,
previewAttachment,
downloadAttachment,
addTransactionOperation
} from '@/api/finance'
export default {
name: 'TransactionDetail',
props: {
transactionId: {
type: [String, Number],
required: true
}
},
data() {
return {
loading: false,
activeTab: 'overall',
detailData: {},
//
uploadDialogVisible: false,
uploadFileList: [],
//
previewDialogVisible: false,
previewLoading: false,
previewUrl: '',
previewBlob: null,
currentPreviewName: '',
isPdf: false,
isImage: false,
//
logsLoading: false,
logsTotal: 0,
logsQuery: {
pageNum: 1,
pageSize: 10,
transactionId: undefined
}
}
},
created() {
this.getDetail()
},
methods: {
//
getDetail() {
this.loading = true
getTransactionDetail(this.transactionId).then(response => {
this.detailData = response.data || {}
this.loading = false
}).catch(() => {
this.loading = false
})
},
//
getOperationLogs() {
this.logsLoading = true
this.logsQuery.transactionId = this.transactionId
getTransactionOperationLogs(this.logsQuery).then(response => {
if (this.detailData.operationRecords) {
this.detailData.operationRecords = response.data.data || []
}
this.logsTotal = response.data.total
this.logsLoading = false
}).catch(() => {
this.logsLoading = false
})
},
//
handleLogsSizeChange(val) {
this.logsQuery.pageSize = val
this.getOperationLogs()
},
//
handleLogsCurrentChange(val) {
this.logsQuery.pageNum = val
this.getOperationLogs()
},
//
formatAmount(amount) {
if (amount === undefined || amount === null) {
return '0.00'
}
return parseFloat(amount).toFixed(2)
},
//
handleAddAttachment() {
this.uploadDialogVisible = true
this.uploadFileList = []
},
//
beforeUpload(file) {
const isValidType = ['image/jpeg', 'image/png', 'application/pdf'].includes(file.type)
const isLt5M = file.size / 1024 / 1024 < 5
if (!isValidType) {
this.$message.error('只能上传JPG/PNG/PDF格式的文件!')
}
if (!isLt5M) {
this.$message.error('文件大小不能超过5MB!')
}
return isValidType && isLt5M
},
//
uploadFile(options) {
//
const file = options.file
// URL
const tempUrl = URL.createObjectURL(file)
//
this.uploadFileList.push({
name: file.name,
file: file,
url: tempUrl
})
},
//
confirmUpload() {
if (this.uploadFileList.length === 0) {
this.$message.warning('请选择要上传的文件')
return
}
const userId = this.$store.getters.userId || ''
const userName = this.$store.getters.name || ''
const uploadPromises = this.uploadFileList.map(fileItem => {
return uploadTransactionAttachment(this.transactionId, fileItem.file, userId, userName)
})
this.loading = true
Promise.all(uploadPromises)
.then(() => {
this.$message.success('附件上传成功')
this.uploadDialogVisible = false
//
this.getDetail()
this.loading = false
})
.catch(() => {
this.$message.error('部分附件上传失败,请重试')
this.loading = false
})
},
//
previewFile(file) {
this.previewLoading = true
this.previewDialogVisible = true
this.currentPreviewName = file.fileName || '附件'
this.previewUrl = ''
this.isPdf = false
this.isImage = false
previewAttachment(file.fileId).then(response => {
//
const contentType = response.headers['content-type']
// blob
const blob = new Blob([response.data], { type: contentType })
this.previewBlob = blob
//
this.isPdf = contentType === 'application/pdf'
this.isImage = contentType.startsWith('image/')
// URL
this.previewUrl = URL.createObjectURL(blob)
this.previewLoading = false
}).catch(error => {
console.error('预览失败', error)
this.$message.error('获取预览数据失败')
this.previewLoading = false
})
},
//
downloadCurrentPreview() {
if (!this.previewBlob) {
this.$message.error('没有可下载的文件')
return
}
const url = URL.createObjectURL(this.previewBlob)
const link = document.createElement('a')
link.href = url
link.setAttribute('download', this.currentPreviewName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
},
//
downloadFile(file) {
downloadAttachment(file.fileId, file.fileName).then(response => {
//
const blob = new Blob([response.data])
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = file.fileName
link.style.display = 'none'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(link.href)
}).catch(() => {
this.$message.warning('无法下载该文件')
})
},
//
deleteFile(index, file) {
this.$confirm('确认删除该附件?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
const userId = this.$store.getters.userId || ''
deleteTransactionAttachment(file.fileId, userId).then(() => {
this.$message.success('删除成功')
if (this.detailData.attachments) {
this.detailData.attachments.splice(index, 1)
}
//
this.getDetail()
})
}).catch(() => {})
},
//
getPaymentModeName(code) {
const paymentModeMap = {
'1': '现金',
'2': '网银转账',
'3': 'POS机',
'4': '支付宝',
'5': '微信',
'6': '转账支票',
'7': '其他方式',
'8': '线上支付'
}
return paymentModeMap[code] || code || '-'
}
}
}
</script>
<style lang="scss" scoped>
.transaction-detail-container {
padding: 0 20px;
.amount {
font-weight: bold;
color: #409EFF;
&.matched {
color: #67C23A;
}
&.unmatched {
color: #E6A23C;
}
}
.box-card {
margin-bottom: 20px;
}
.upload-area {
text-align: center;
padding: 20px 0;
}
.empty-data {
color: #909399;
text-align: center;
padding: 30px 0;
}
.delete-btn {
color: #F56C6C;
}
.preview-container {
min-height: 500px;
max-height: 700px;
overflow: auto;
.preview-iframe {
width: 100%;
height: 500px;
border: none;
}
.preview-image {
display: flex;
justify-content: center;
align-items: center;
min-height: 300px;
}
.preview-error {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
color: #909399;
i {
font-size: 48px;
margin-bottom: 20px;
}
p {
font-size: 16px;
}
.preview-actions {
margin-top: 20px;
}
}
}
}
</style>

View File

@ -0,0 +1,352 @@
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" class="search-form">
<el-form-item label="借贷标" prop="dcFlagCode">
<el-select v-model="queryParams.dcFlagCode" placeholder="请选择借贷标" clearable style="width: 200px">
<el-option label="借(支出)" value="0" />
<el-option label="贷(收入)" value="1" />
</el-select>
</el-form-item>
<el-form-item label="支付方式" prop="payModeName">
<el-select v-model="queryParams.payModeCode" placeholder="请选择支付方式" clearable style="width: 200px">
<el-option label="现金" value="1" />
<el-option label="网银转账" value="2" />
<el-option label="POS机" value="3" />
<el-option label="支付宝" value="4" />
<el-option label="微信" value="5" />
<el-option label="转账支票" value="6" />
<el-option label="其他方式" value="7" />
<el-option label="线上支付" value="8" />
</el-select>
</el-form-item>
<el-form-item label="流水账户" prop="accountId">
<el-select v-model="queryParams.accountId" placeholder="请选择流水账户" clearable filterable style="width: 200px">
<el-option v-for="item in accountOptions" :key="item.id" :label="item.accountName" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="创建人员" prop="createUserName">
<el-input v-model="queryParams.createUserName" placeholder="请输入创建人员" clearable style="width: 200px" />
</el-form-item>
<el-form-item label="入账时间" prop="inaccDate">
<el-date-picker
v-model="accountDateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
style="width: 240px">
</el-date-picker>
</el-form-item>
<el-form-item label="创建时间" prop="createDate">
<el-date-picker
v-model="createDateRange"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
style="width: 240px">
</el-date-picker>
</el-form-item>
<el-form-item label="发生额" prop="occuAmt">
<el-input-number v-model="queryParams.occuAmtMin" placeholder="最小值" :min="0" :precision="2" style="width: 110px" controls-position="right" />
<span style="margin: 0 5px;">-</span>
<el-input-number v-model="queryParams.occuAmtMax" placeholder="最大值" :min="0" :precision="2" style="width: 110px" controls-position="right" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
</el-form-item>
<el-button type="warning" icon="el-icon-download" style="float: right;" @click="handleExport">导出</el-button>
</el-form>
<!-- 表格区域 -->
<el-table v-loading="loading" :data="transactionList" border>
<el-table-column label="项目楼宇名称" prop="projectName" min-width="120" show-overflow-tooltip>
<template slot-scope="scope">
{{ scope.row.projectName }} - {{ scope.row.buildingName }}
</template>
</el-table-column>
<el-table-column label="房号" prop="roomNo" min-width="100" show-overflow-tooltip />
<el-table-column label="对方账号" prop="opsAccname" min-width="150" show-overflow-tooltip />
<el-table-column label="入账日期" prop="inaccDate" min-width="120" align="center" />
<el-table-column label="对方名称" prop="unitName" min-width="120" show-overflow-tooltip />
<el-table-column label="借贷标" min-width="100" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.dcFlagCode === '0' ? 'danger' : 'success'">
{{ scope.row.dcFlagCode === '0' ? '借(支出)' : '贷(收入)' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="发生额" prop="occuAmt" min-width="100" align="right">
<template slot-scope="scope">
{{ scope.row.occuAmt ? parseFloat(scope.row.occuAmt).toFixed(2) : '0.00' }}
</template>
</el-table-column>
<el-table-column label="币种(单位)" prop="currCode" min-width="100" align="center">
<template slot-scope="scope">
{{ getCurrencyName(scope.row.currCode) }}
</template>
</el-table-column>
<el-table-column label="支付方式" prop="payModeName" min-width="100" align="center">
<template slot-scope="scope">
{{ getPaymentModeName(scope.row.payModeName) }}
</template>
</el-table-column>
<el-table-column label="创建人" prop="createUserId" min-width="100" show-overflow-tooltip />
<el-table-column label="状态" prop="transactionStatus" min-width="100" align="center">
<template slot-scope="scope">
<el-tag :type="getStatusType(scope.row.transactionStatus)">
{{ getStatusName(scope.row.transactionStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建日期" prop="createTime" min-width="120" align="center" />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleViewDetail(scope.row)">查看</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<!-- 分页区域 -->
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryParams.pageNum"
:page-sizes="[10, 20, 30, 50]"
:page-size="queryParams.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
<!-- 流水详情对话框 -->
<el-dialog title="收支流水详情" :visible.sync="detailVisible" width="80%" append-to-body>
<transaction-detail v-if="detailVisible" :transactionId="currentTransaction.id" />
</el-dialog>
</div>
</template>
<script>
import { getTransactionList, exportTransactionList, getAllFinanceAccounts } from '@/api/finance'
import TransactionDetail from './components/TransactionDetail'
export default {
name: 'Transaction',
components: {
TransactionDetail
},
data() {
return {
//
loading: false,
//
transactionList: [],
//
total: 0,
//
accountDateRange: [],
//
createDateRange: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
dcFlagCode: undefined,
payModeCode: undefined,
accountId: undefined,
createUserName: undefined,
inaccDateStart: undefined,
inaccDateEnd: undefined,
createDateStart: undefined,
createDateEnd: undefined,
occuAmtMin: undefined,
occuAmtMax: undefined
},
//
accountOptions: [],
//
detailVisible: false,
//
currentTransaction: {}
}
},
created() {
this.getList()
// this.getAccountOptions()
},
methods: {
//
getList() {
this.loading = true
//
const queryParams = { ...this.queryParams }
//
if (this.accountDateRange && this.accountDateRange.length > 0) {
queryParams.inaccDateStart = this.accountDateRange[0]
queryParams.inaccDateEnd = this.accountDateRange[1]
}
//
if (this.createDateRange && this.createDateRange.length > 0) {
queryParams.createDateStart = this.createDateRange[0]
queryParams.createDateEnd = this.createDateRange[1]
}
//
if (queryParams.payModeCode) {
queryParams.payModeName = queryParams.payModeCode
delete queryParams.payModeCode
}
getTransactionList(queryParams).then(response => {
this.transactionList = response.data.data || []
this.total = response.data.total
this.loading = false
}).catch(() => {
this.loading = false
})
},
//
getAccountOptions() {
getAllFinanceAccounts().then(response => {
this.accountOptions = response.data || []
})
},
//
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
//
resetQuery() {
this.accountDateRange = []
this.createDateRange = []
this.$refs.queryForm.resetFields()
this.handleQuery()
},
//
handleSizeChange(val) {
this.queryParams.pageSize = val
this.getList()
},
//
handleCurrentChange(val) {
this.queryParams.pageNum = val
this.getList()
},
//
handleViewDetail(row) {
this.currentTransaction = row
this.detailVisible = true
},
//
handleExport() {
//
const exportParams = { ...this.queryParams }
delete exportParams.pageNum
delete exportParams.pageSize
//
if (this.accountDateRange && this.accountDateRange.length > 0) {
exportParams.inaccDateStart = this.accountDateRange[0]
exportParams.inaccDateEnd = this.accountDateRange[1]
}
//
if (this.createDateRange && this.createDateRange.length > 0) {
exportParams.createDateStart = this.createDateRange[0]
exportParams.createDateEnd = this.createDateRange[1]
}
this.$confirm('是否确认导出所有数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
exportTransactionList(exportParams).then(response => {
//
const blob = new Blob([response.data])
const fileName = `收支流水列表_${new Date().getTime()}.xlsx`
if (window.navigator.msSaveOrOpenBlob) {
// IE
navigator.msSaveBlob(blob, fileName)
} else {
// IE
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = fileName
link.style.display = 'none'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(link.href)
}
})
})
},
//
getCurrencyName(currency) {
const currencyMap = {
'156': '人民币',
'840': '美元',
'978': '欧元',
'826': '英镑',
'392': '日元'
}
return currencyMap[currency] || currency || '人民币'
},
//
getStatusType(status) {
const statusMap = {
'0': 'info',
'1': 'success'
}
return statusMap[status] || 'info'
},
//
getStatusName(status) {
const statusMap = {
'0': '处理中',
'1': '已完成'
}
return statusMap[status] || '未知状态'
},
//
getPaymentModeName(code) {
const paymentModeMap = {
'1': '现金',
'2': '网银转账',
'3': 'POS机',
'4': '支付宝',
'5': '微信',
'6': '转账支票',
'7': '其他方式',
'8': '线上支付'
}
return paymentModeMap[code] || code || '-'
}
}
}
</script>
<style lang="scss" scoped>
.app-container {
padding: 20px;
.search-form {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
}
</style>

View File

@ -63,7 +63,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="所属地区"> <el-form-item label="所属地区">
<el-input v-model="queryParams.region" placeholder="请输入所属地区" clearable size="small" /> <region-selector v-model="queryParams.region" :value="queryParams.region" placeholder="请选择所属地区" size="small" @change="handleRegionChange" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@ -133,7 +133,7 @@
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="所属地区" prop="region"> <el-form-item label="所属地区" prop="region">
<el-input v-model="form.region" placeholder="请输入所属地区" /> <region-selector v-model="form.region" placeholder="请选择所属地区" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -223,9 +223,13 @@
<script> <script>
import { getProjectList, getProjectDetail, addProject, updateProject, deleteProject, getAllProjectStatistics, getTagByType } from '@/api/project' import { getProjectList, getProjectDetail, addProject, updateProject, deleteProject, getAllProjectStatistics, getTagByType } from '@/api/project'
import RegionSelector from '@/components/RegionSelector'
export default { export default {
name: 'ProjectList', name: 'ProjectList',
components: {
RegionSelector
},
data() { data() {
return { return {
// //
@ -338,7 +342,9 @@ export default {
handleAdd() { handleAdd() {
this.dialogTitle = '新增项目' this.dialogTitle = '新增项目'
// //
this.form = {} this.form = {
region: '' // region
}
this.dialogVisible = true this.dialogVisible = true
}, },
/** 修改按钮操作 */ /** 修改按钮操作 */
@ -474,6 +480,10 @@ export default {
// //
this.form = {} this.form = {}
this.$refs['form'].clearValidate() this.$refs['form'].clearValidate()
},
handleRegionChange(regionText) {
this.queryParams.region = regionText
} }
} }
} }

View File

@ -12,7 +12,7 @@ module.exports = {
}, },
// proxy: { // proxy: {
// '/': { // '/': {
// target: 'http://192.168.137.214:8082', // target: 'http://192.168.137.214:8080',
// changeOrigin: true, // changeOrigin: true,
// pathRewrite: { // pathRewrite: {
// '^/api': '/api' // '^/api': '/api'