业务财务相关页面对接

This commit is contained in:
zengqiyang 2025-04-23 09:10:08 +08:00
parent 3ac7dbff00
commit 9068135f26
19 changed files with 3318 additions and 204 deletions

View File

@ -1,4 +1,4 @@
NODE_ENV = 'development' NODE_ENV = 'development'
# 开发环境API地址 # 开发环境API地址
VUE_APP_BASE_API = http://192.168.137.3:8080/api VUE_APP_BASE_API = http://192.168.137.3:8080/api/api

View File

@ -68,7 +68,7 @@ export function getLabelFields() {
]; ];
return Promise.resolve({ return Promise.resolve({
code: '000000', code: '0000000000000000',
msg: 'success', msg: 'success',
data: fieldData data: fieldData
}); });

View File

@ -337,4 +337,113 @@ export function getFeeTypeTree(params) {
method: 'get', method: 'get',
params params
}) })
}
// 预览账单
export function previewBill(data) {
return request({
url: '/finance/charging-standard/bill/preview',
method: 'post',
headers: {
'Content-Type': 'application/json'
},
data
})
}
// 生成账单
export function generateBill(data) {
return request({
url: '/finance/charging-standard/bill/generate',
method: 'post',
headers: {
'Content-Type': 'application/json'
},
data
})
}
/**
* 分页查询账单-收费标准关联表数据
* @param {Object} data - 查询参数
* @returns {Promise} - 返回Promise对象
*/
export function getBillChargingStandardRelPage(data) {
return request({
url: '/finance/bill-charging-standard-rel/page',
method: 'post',
data
})
}
/**
* 获取特定标准ID的绑定房间账单列表
* @param {string} standardId 收费标准ID
*/
export function getBillsByStandardId(standardId) {
return request({
url: `/finance/bill-charging-standard-rel/${standardId}/bills`,
method: 'get'
})
}
// 账单管理接口
export function addBill(data) {
return request({
url: '/bill/add',
method: 'post',
data
})
}
export function getBillList(data) {
return request({
url: '/bill/list',
method: 'post',
data
})
}
export function getBillDetail(billId) {
return request({
url: `/bill/detail/${billId}`,
method: 'get'
})
}
export function uploadBillAttachment(billId, file) {
const formData = new FormData()
formData.append('billId', billId)
formData.append('file', file)
return request({
url: '/bill/attachment/upload',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
export function deleteBillAttachment(attachmentId) {
return request({
url: `/bill/attachment/${attachmentId}`,
method: 'delete'
})
}
export function getBillAttachmentList(billId) {
return request({
url: `/bill/attachment/list/${billId}`,
method: 'get'
})
}
export function exportBillList(data) {
return request({
url: '/bill/export',
method: 'post',
data,
responseType: 'blob'
})
} }

View File

@ -24,6 +24,12 @@ export default {
component: () => import('@/views/finance/chargeStandard/index.vue'), component: () => import('@/views/finance/chargeStandard/index.vue'),
name: 'ChargeStandard', name: 'ChargeStandard',
meta: { title: '收费标准', icon: 'el-icon-price-tag' } meta: { title: '收费标准', icon: 'el-icon-price-tag' }
},
{
path: 'billList',
component: () => import('@/views/finance/billList/index.vue'),
name: 'BillList',
meta: { title: '所有账单', icon: 'el-icon-notebook-2' }
} }
] ]
} }

View File

@ -4,8 +4,8 @@ import { API_SUCCESS_CODE } from './constants'
// 创建axios实例 // 创建axios实例
const service = axios.create({ const service = axios.create({
baseURL: '/', // 修改为相对路径,使用代理 // baseURL: '/', // 修改为相对路径,使用代理
// baseURL: process.env.VUE_APP_BASE_API, // 使用环境变量中的接口地址 baseURL: process.env.VUE_APP_BASE_API, // 使用环境变量中的接口地址
timeout: 10000 // 请求超时时间 timeout: 10000 // 请求超时时间
}) })

View File

@ -169,6 +169,7 @@
<script> <script>
import { listClassification, getClassification, addClassification, updateClassification, delClassification, enableClassification, disableClassification, getClassificationTree, checkClassificationInUse, getChildClassifications, delClassificationBatch } from '@/api/asset/classification' import { listClassification, getClassification, addClassification, updateClassification, delClassification, enableClassification, disableClassification, getClassificationTree, checkClassificationInUse, getChildClassifications, delClassificationBatch } from '@/api/asset/classification'
import { API_SUCCESS_CODE } from '@/utils/constants'
export default { export default {
name: 'AssetClassification', name: 'AssetClassification',
@ -262,7 +263,7 @@ export default {
status: undefined // status: undefined //
} }
getClassificationTree(params).then(res => { getClassificationTree(params).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
// //
this.classTree = this.processTreeData(res.data) this.classTree = this.processTreeData(res.data)
@ -363,7 +364,7 @@ export default {
getList() { getList() {
this.loading = true this.loading = true
listClassification(this.queryParams).then(res => { listClassification(this.queryParams).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
// API // API
this.classList = this.convertSnakeToCamel(res.data.list) this.classList = this.convertSnakeToCamel(res.data.list)
this.total = res.data.total this.total = res.data.total
@ -420,7 +421,7 @@ export default {
pageNum: this.queryParams.pageNum, pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize pageSize: this.queryParams.pageSize
}).then(res => { }).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.classList = this.convertSnakeToCamel(res.data.list) this.classList = this.convertSnakeToCamel(res.data.list)
this.total = res.data.total this.total = res.data.total
} else { } else {
@ -472,7 +473,7 @@ export default {
handleEdit(row) { handleEdit(row) {
const id = row.id const id = row.id
getClassification(id).then(res => { getClassification(id).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
// API使 // API使
this.form = { this.form = {
id: res.data.id, id: res.data.id,
@ -524,7 +525,7 @@ export default {
const method = this.form.id ? updateClassification : addClassification const method = this.form.id ? updateClassification : addClassification
method(submitData).then(res => { method(submitData).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.$message.success('操作成功') this.$message.success('操作成功')
this.dialogVisible = false this.dialogVisible = false
@ -556,14 +557,14 @@ export default {
}).then(() => { }).then(() => {
// 使 // 使
checkClassificationInUse(row.id).then(res => { checkClassificationInUse(row.id).then(res => {
if (res.code === '000000' && res.data) { if (res.code === API_SUCCESS_CODE && res.data) {
this.$message.error('该分类已被使用,无法删除') this.$message.error('该分类已被使用,无法删除')
return return
} }
// 使 // 使
const lastModUserId = this.$store.getters.userId || '' const lastModUserId = this.$store.getters.userId || ''
delClassification(row.id, lastModUserId).then(res => { delClassification(row.id, lastModUserId).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.$message.success('删除成功') this.$message.success('删除成功')
// //
this.getTree() this.getTree()
@ -590,7 +591,7 @@ export default {
const lastModUserId = this.$store.getters.userId || '' const lastModUserId = this.$store.getters.userId || ''
const method = row.status === '1' ? disableClassification : enableClassification const method = row.status === '1' ? disableClassification : enableClassification
method(row.id, lastModUserId).then(res => { method(row.id, lastModUserId).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.$message.success(`${action}成功`) this.$message.success(`${action}成功`)
// //
this.getTree() this.getTree()
@ -663,7 +664,7 @@ export default {
// //
this.loading = true this.loading = true
delClassificationBatch(selectedIds, lastModUserId).then(res => { delClassificationBatch(selectedIds, lastModUserId).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.$message.success('批量删除成功') this.$message.success('批量删除成功')
// //
this.getTree() this.getTree()

View File

@ -48,6 +48,7 @@
import { getAsset } from '@/api/asset/inventory' import { getAsset } from '@/api/asset/inventory'
import { getClassificationTree } from '@/api/asset/classification' import { getClassificationTree } from '@/api/asset/classification'
import { getLocationTree } from '@/api/asset/location' import { getLocationTree } from '@/api/asset/location'
import { API_SUCCESS_CODE } from '@/utils/constants'
export default { export default {
name: 'AssetDetail', name: 'AssetDetail',
@ -89,7 +90,7 @@ export default {
this.loading = true this.loading = true
getAsset(this.assetId).then(response => { getAsset(this.assetId).then(response => {
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.assetDetail = response.data || {} this.assetDetail = response.data || {}
console.log('资产详情数据:', this.assetDetail) console.log('资产详情数据:', this.assetDetail)
} else { } else {
@ -134,7 +135,7 @@ export default {
/** 获取资产分类树形选项 */ /** 获取资产分类树形选项 */
getClassificationOptions() { getClassificationOptions() {
getClassificationTree({ status: '1' }).then(response => { getClassificationTree({ status: '1' }).then(response => {
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.classificationOptions = this.processClassificationTree(response.data || []) this.classificationOptions = this.processClassificationTree(response.data || [])
} }
}) })
@ -164,7 +165,7 @@ export default {
/** 获取位置树选项 */ /** 获取位置树选项 */
getLocationOptions() { getLocationOptions() {
getLocationTree().then(response => { getLocationTree().then(response => {
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.locationOptions = this.processLocationTree(response.data || []) this.locationOptions = this.processLocationTree(response.data || [])
} }
}) })

View File

@ -227,7 +227,6 @@ import { getAsset, addAsset, updateAsset, listCompanies, listUsers, uploadAssetI
import { getClassificationTree } from '@/api/asset/classification' import { getClassificationTree } from '@/api/asset/classification'
import { getLocationTree } from '@/api/asset/location' import { getLocationTree } from '@/api/asset/location'
import { API_SUCCESS_CODE } from '@/utils/constants' import { API_SUCCESS_CODE } from '@/utils/constants'
export default { export default {
name: 'AssetForm', name: 'AssetForm',
props: { props: {

View File

@ -37,6 +37,7 @@
<script> <script>
import { getLabelConfig } from '@/api/asset/inventory' import { getLabelConfig } from '@/api/asset/inventory'
import QrcodeVue from 'qrcode.vue' import QrcodeVue from 'qrcode.vue'
import { API_SUCCESS_CODE } from '@/utils/constants'
export default { export default {
name: 'AssetLabelPrint', name: 'AssetLabelPrint',
@ -93,7 +94,7 @@ export default {
getLabelConfig() { getLabelConfig() {
this.loading = true this.loading = true
getLabelConfig().then(response => { getLabelConfig().then(response => {
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.labelConfig = response.data this.labelConfig = response.data
// API使API // API使API

View File

@ -267,6 +267,7 @@ import { getLocationTree } from '@/api/asset/location'
import AssetDetail from './components/AssetDetail' import AssetDetail from './components/AssetDetail'
import AssetForm from './components/AssetForm' import AssetForm from './components/AssetForm'
import AssetLabelPrint from './components/AssetLabelPrint' import AssetLabelPrint from './components/AssetLabelPrint'
import { API_SUCCESS_CODE } from '@/utils/constants'
export default { export default {
name: 'AssetInventory', name: 'AssetInventory',
@ -449,7 +450,7 @@ export default {
getList() { getList() {
this.loading = true this.loading = true
listAssets(this.queryParams).then(response => { listAssets(this.queryParams).then(response => {
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.assetList = response.data.list || [] this.assetList = response.data.list || []
this.total = response.data.total this.total = response.data.total
} }
@ -471,7 +472,7 @@ export default {
/** 获取资产分类树形选项 */ /** 获取资产分类树形选项 */
getClassificationOptions() { getClassificationOptions() {
getClassificationTree({ status: '1' }).then(response => { getClassificationTree({ status: '1' }).then(response => {
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.classificationOptions = this.processClassificationTree(response.data || []) this.classificationOptions = this.processClassificationTree(response.data || [])
} }
}) })
@ -480,7 +481,7 @@ export default {
/** 获取资产位置树形选项 */ /** 获取资产位置树形选项 */
getLocationOptions() { getLocationOptions() {
getLocationTree().then(response => { getLocationTree().then(response => {
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.locationOptions = this.processLocationTree(response.data || []) this.locationOptions = this.processLocationTree(response.data || [])
} }
}) })
@ -529,7 +530,7 @@ export default {
/** 获取公司选项 */ /** 获取公司选项 */
getCompanyOptions() { getCompanyOptions() {
listCompanies().then(response => { listCompanies().then(response => {
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.companyOptions = response.data || [] this.companyOptions = response.data || []
} }
}) })
@ -541,7 +542,7 @@ export default {
this.adminLoading = true this.adminLoading = true
listUsers({ name: query }).then(response => { listUsers({ name: query }).then(response => {
this.adminLoading = false this.adminLoading = false
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.adminOptions = response.data || [] this.adminOptions = response.data || []
} }
}).catch(() => { }).catch(() => {
@ -672,7 +673,7 @@ export default {
}).then(() => { }).then(() => {
return deleteAsset(row.assetCode) return deleteAsset(row.assetCode)
}).then(response => { }).then(response => {
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.$message.success('删除成功') this.$message.success('删除成功')
this.getList() this.getList()
} }
@ -694,7 +695,7 @@ export default {
}).then(() => { }).then(() => {
return deleteAssets(assetCodes) return deleteAssets(assetCodes)
}).then(response => { }).then(response => {
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.$message.success('批量删除成功') this.$message.success('批量删除成功')
this.getList() this.getList()
} }
@ -745,7 +746,7 @@ export default {
this.upload.isUploading = false this.upload.isUploading = false
this.$refs.upload.clearFiles() this.$refs.upload.clearFiles()
this.upload.open = false this.upload.open = false
if (response.code === '000000') { if (response.code === API_SUCCESS_CODE) {
this.$message.success('导入成功' + (response.data && response.data.successCount ? response.data.successCount : 0) + '条数据') this.$message.success('导入成功' + (response.data && response.data.successCount ? response.data.successCount : 0) + '条数据')
this.getList() this.getList()
} }

View File

@ -109,6 +109,7 @@ import {
listLabelTemplates, addLabelTemplate, updateLabelTemplate, listLabelTemplates, addLabelTemplate, updateLabelTemplate,
getLabelFields, getFieldSampleValues getLabelFields, getFieldSampleValues
} from '@/api/asset/label' } from '@/api/asset/label'
import { API_SUCCESS_CODE } from '@/utils/constants'
export default { export default {
name: 'AssetLabel', name: 'AssetLabel',
@ -153,7 +154,7 @@ export default {
try { try {
this.loading = true; this.loading = true;
const res = await listLabelTemplates(); const res = await listLabelTemplates();
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
if (res.data) { if (res.data) {
const labelData = res.data; const labelData = res.data;
this.labelId = labelData.id; this.labelId = labelData.id;
@ -209,7 +210,7 @@ export default {
async getFieldOptions() { async getFieldOptions() {
try { try {
const res = await getLabelFields(); const res = await getLabelFields();
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.availableFields = res.data || []; this.availableFields = res.data || [];
console.log("获取到的字段选项: ", this.availableFields); console.log("获取到的字段选项: ", this.availableFields);
} else { } else {
@ -342,7 +343,7 @@ export default {
savePromise.then(res => { savePromise.then(res => {
this.loading = false; this.loading = false;
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.$message.success('标签设置保存成功'); this.$message.success('标签设置保存成功');
// //
this.getLabelSettings(); this.getLabelSettings();

View File

@ -174,6 +174,7 @@
<script> <script>
import { listLocation, getLocation, addLocation, updateLocation, delLocation, enableLocation, disableLocation, getLocationTree, checkLocationInUse, getChildLocations, delLocationBatch, checkLocationCode } from '@/api/asset/location' import { listLocation, getLocation, addLocation, updateLocation, delLocation, enableLocation, disableLocation, getLocationTree, checkLocationInUse, getChildLocations, delLocationBatch, checkLocationCode } from '@/api/asset/location'
import { API_SUCCESS_CODE } from '@/utils/constants'
export default { export default {
name: 'AssetLocation', name: 'AssetLocation',
@ -245,7 +246,7 @@ export default {
return return
} }
checkLocationCode(this.form.id, value).then(res => { checkLocationCode(this.form.id, value).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
if (res.data) { if (res.data) {
callback() callback()
} else { } else {
@ -265,7 +266,7 @@ export default {
status: undefined // status: undefined //
} }
getLocationTree(params).then(res => { getLocationTree(params).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
// //
this.locationTree = this.processTreeData(res.data) this.locationTree = this.processTreeData(res.data)
@ -387,7 +388,7 @@ export default {
getList() { getList() {
this.loading = true this.loading = true
listLocation(this.queryParams).then(res => { listLocation(this.queryParams).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
// API // API
this.locationList = this.convertSnakeToCamel(res.data.list) this.locationList = this.convertSnakeToCamel(res.data.list)
this.total = res.data.total this.total = res.data.total
@ -410,7 +411,7 @@ export default {
pageNum: this.queryParams.pageNum, pageNum: this.queryParams.pageNum,
pageSize: this.queryParams.pageSize pageSize: this.queryParams.pageSize
}).then(res => { }).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.locationList = this.convertSnakeToCamel(res.data.list) this.locationList = this.convertSnakeToCamel(res.data.list)
this.total = res.data.total this.total = res.data.total
} else { } else {
@ -464,7 +465,7 @@ export default {
handleEdit(row) { handleEdit(row) {
const id = row.id const id = row.id
getLocation(id).then(res => { getLocation(id).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
// API使 // API使
this.form = { this.form = {
id: res.data.id, id: res.data.id,
@ -518,7 +519,7 @@ export default {
const method = this.form.id ? updateLocation : addLocation const method = this.form.id ? updateLocation : addLocation
method(submitData).then(res => { method(submitData).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.$message.success('操作成功') this.$message.success('操作成功')
this.dialogVisible = false this.dialogVisible = false
@ -550,14 +551,14 @@ export default {
}).then(() => { }).then(() => {
// 使 // 使
checkLocationInUse(row.id).then(res => { checkLocationInUse(row.id).then(res => {
if (res.code === '000000' && res.data) { if (res.code === API_SUCCESS_CODE && res.data) {
this.$message.error('该位置已被使用,无法删除') this.$message.error('该位置已被使用,无法删除')
return return
} }
// 使 // 使
const lastModUserId = this.$store.getters.userId || '' const lastModUserId = this.$store.getters.userId || ''
delLocation(row.id, lastModUserId).then(res => { delLocation(row.id, lastModUserId).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.$message.success('删除成功') this.$message.success('删除成功')
// //
this.getTree() this.getTree()
@ -584,7 +585,7 @@ export default {
const lastModUserId = this.$store.getters.userId || '' const lastModUserId = this.$store.getters.userId || ''
const method = row.status === '1' ? disableLocation : enableLocation const method = row.status === '1' ? disableLocation : enableLocation
method(row.id, lastModUserId).then(res => { method(row.id, lastModUserId).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.$message.success(`${action}成功`) this.$message.success(`${action}成功`)
// //
this.getTree() this.getTree()
@ -657,7 +658,7 @@ export default {
// //
this.loading = true this.loading = true
delLocationBatch(selectedIds, lastModUserId).then(res => { delLocationBatch(selectedIds, lastModUserId).then(res => {
if (res.code === '000000') { if (res.code === API_SUCCESS_CODE) {
this.$message.success('批量删除成功') this.$message.success('批量删除成功')
// //
this.getTree() this.getTree()

View File

@ -0,0 +1,758 @@
<template>
<div class="add-bill-container">
<el-tabs v-model="activeTab">
<el-tab-pane label="收费模式" name="chargeMode">
<el-form ref="chargeModeForm" :model="billForm" :rules="chargeModeRules" label-width="120px">
<el-form-item label="含税规则" prop="taxInclusiveRule" required>
<el-radio-group v-model="billForm.taxInclusiveRule">
<el-radio label="含税">含税</el-radio>
<el-radio label="不含税">不含税</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="税率(%)" prop="taxRate" required>
<el-input-number v-model="billForm.taxRate" :min="0" :max="100" :precision="2" style="width: 200px"></el-input-number>
</el-form-item>
<el-form-item label="特殊账单类型" prop="specialBillType" required>
<el-radio-group v-model="billForm.specialBillType">
<el-radio label="正常">正常</el-radio>
<el-radio label="罚金">罚金</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="滞纳金起算天数" prop="lateFeeStartDays">
<el-input-number v-model="billForm.lateFeeStartDays" :min="0" :precision="0" style="width: 200px"></el-input-number>
</el-form-item>
<el-form-item label="滞纳金比例(%/天)" prop="lateFeeRate">
<el-input-number v-model="billForm.lateFeeRate" :min="0" :max="100" :precision="10" style="width: 200px"></el-input-number>
</el-form-item>
<el-form-item label="滞纳金上限(%)" prop="lateFeeLimit">
<el-input-number v-model="billForm.lateFeeLimit" :min="0" :max="100" :precision="10" style="width: 200px"></el-input-number>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="账单信息" name="billInfo">
<el-form ref="formExtension" :model="formExtension" :rules="formExtensionRules" label-width="120px" style="margin-bottom: 20px;">
<el-form-item label="费用类型" prop="feeTypeSelected" required>
<el-cascader v-model="formExtension.feeTypeSelected" :options="feeTypeOptions" :props="{
label: 'label',
value: 'id',
children: 'children',
expandTrigger: 'hover'
}"
clearable filterable placeholder="请选择费用类型" @change="handleFeeTypeChange" style="width: 400px">
</el-cascader>
</el-form-item>
<el-form-item label="计费周期" prop="billingPeriod" required>
<el-date-picker
v-model="formExtension.billingPeriod"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="yyyy-MM-dd"
@change="handleBillingPeriodChange"
style="width: 400px">
</el-date-picker>
</el-form-item>
</el-form>
<el-form ref="billInfoForm" :model="billForm" :rules="billInfoRules" label-width="120px">
<el-form-item label="关联合同" prop="contractId">
<el-select v-model="billForm.contractId" placeholder="请选择关联合同" filterable remote clearable
:remote-method="searchContracts" style="width: 400px">
<el-option v-for="item in contractOptions" :key="item.contractId"
:label="item.contractNumber + ' - ' + item.customerName" :value="item.contractId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="付款方" prop="payeeId" required>
<el-select v-model="billForm.payeeId" placeholder="请选择付款方" filterable remote clearable
:remote-method="searchPayees" @change="handlePayeeChange" style="width: 400px">
<el-option v-for="item in payeeOptions" :key="item.id"
:label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="币种" prop="currCode" required>
<el-input v-model="billForm.currCode" disabled style="width: 400px"></el-input>
</el-form-item>
<el-form-item label="应收金额" prop="receivableAmount" required>
<el-input-number v-model="billForm.receivableAmount" :min="0" :precision="2" style="width: 400px"></el-input-number>
</el-form-item>
<el-form-item label="应收日期" prop="receivableDate" required>
<el-date-picker
v-model="billForm.receivableDate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 400px">
</el-date-picker>
</el-form-item>
<el-form-item label="所属公司" prop="companyId" required>
<el-select v-model="billForm.companyId" placeholder="请选择所属公司" filterable @change="handleCompanyChange" style="width: 400px">
<el-option v-for="item in companyOptions" :key="item.id"
:label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="收支账户" prop="accountId" required>
<el-select v-model="billForm.accountId" placeholder="请选择收支账户" filterable style="width: 400px">
<el-option v-for="item in accountOptions" :key="item.id"
:label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="账单备注" prop="billRemark">
<el-input type="textarea" v-model="billForm.billRemark" placeholder="请输入账单备注" rows="3" style="width: 400px"></el-input>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane label="房源信息" name="roomInfo">
<el-tree
ref="roomTree"
:data="roomTreeData"
show-checkbox
node-key="id"
:props="{ label: 'label', children: 'children' }"
:default-checked-keys="selectedRoomIds"
@check="handleRoomTreeCheck">
</el-tree>
</el-tab-pane>
<el-tab-pane label="账单附件" name="attachment">
<el-upload
class="upload-area"
action="#"
:http-request="uploadFile"
:file-list="fileList"
: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>
<el-table v-loading="attachmentLoading" :data="fileList" border style="width: 100%; margin-top: 20px">
<el-table-column prop="name" label="文件名" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="operatorName" label="操作人" width="150"></el-table-column>
<el-table-column prop="operateTime" label="操作时间" width="180"></el-table-column>
<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)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { getFeeTypeTree } from '@/api/finance'
export default {
name: 'AddBill',
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
activeTab: 'chargeMode',
//
billForm: {
contractId: '',
contractNumber: '',
payeeId: '',
payeeName: '',
payeeContact: '',
feeCategoryId: '',
feeTypeId: '',
feeTypeName: '',
currCode: '156', //
billingStartDate: '',
billingEndDate: '',
receivableAmount: 0,
price: 0,
priceU: '元/月',
taxInclusiveRule: '含税',
taxRate: 0,
receivableDate: '',
specialBillType: '正常',
lateFeeStartDays: 0,
lateFeeRate: 0,
lateFeeLimit: 0,
companyId: '',
companyName: '',
accountId: '',
billRemark: '',
roomInfoList: []
},
//
chargeModeRules: {
taxInclusiveRule: [
{ required: true, message: '请选择含税规则', trigger: 'change' }
],
taxRate: [
{ required: true, message: '请输入税率', trigger: 'blur' }
],
specialBillType: [
{ required: true, message: '请选择特殊账单类型', trigger: 'change' }
]
},
billInfoRules: {
payeeId: [
{ required: true, message: '请选择付款方', trigger: 'change' }
],
companyId: [
{ required: true, message: '请选择所属公司', trigger: 'change' }
],
accountId: [
{ required: true, message: '请选择收支账户', trigger: 'change' }
],
receivableAmount: [
{ required: true, message: '请输入应收金额', trigger: 'blur' }
],
receivableDate: [
{ required: true, message: '请选择应收日期', trigger: 'change' }
]
},
//
formExtension: {
feeTypeSelected: null,
billingPeriod: []
},
formExtensionRules: {
feeTypeSelected: [
{ required: true, message: '请选择费用类型', trigger: 'change' }
],
billingPeriod: [
{ type: 'array', required: true, message: '请选择计费周期', trigger: 'change' }
]
},
//
contractOptions: [],
payeeOptions: [],
feeTypeOptions: [],
companyOptions: [],
accountOptions: [],
roomTreeData: [],
selectedRoomIds: [],
//
fileList: [],
attachmentLoading: false
}
},
created() {
this.getFeeTypeOptions()
this.getCompanyOptions()
this.getRoomTreeData()
},
methods: {
//
getFeeTypeOptions() {
getFeeTypeTree({ level: 2 }).then(response => {
if (response.code === '0000000000000000') {
//
this.feeTypeOptions = this.processTreeData(response.data)
}
})
},
//
processTreeData(data) {
if (!data || !Array.isArray(data) || data.length === 0) {
console.warn('无费用类型数据')
return []
}
return data.map(category => {
// id
const categoryId = category.id || Math.random().toString(36).substr(2, 9)
//
let children = []
if (category.financeFeeTypes && Array.isArray(category.financeFeeTypes)) {
children = category.financeFeeTypes.map(feeType => {
return {
id: feeType.id || Math.random().toString(36).substr(2, 9),
label: feeType.feeTypeName || '未命名费用',
categoryId: categoryId,
// 便使
feeTypeName: feeType.feeTypeName || '未命名费用',
//
children: null
}
})
}
return {
id: categoryId,
label: category.categoryName || '未命名分类',
categoryName: category.categoryName || '未命名分类',
children: children.length > 0 ? children : null
}
}).filter(item => item.children && item.children.length > 0)
},
//
getCompanyOptions() {
// API
this.companyOptions = [
{ id: 'C001', name: '智慧园区物业管理有限公司' }
]
},
//
getAccountOptions(companyId) {
// API
this.accountOptions = [
{ id: 1, name: '运营账户-工商银行' },
{ id: 2, name: '日常账户-建设银行' }
]
},
//
searchContracts(query) {
if (query) {
// API
this.contractOptions = [
{ contractId: 'CT001', contractNumber: 'HT2023001', customerName: '张三' },
{ contractId: 'CT002', contractNumber: 'HT2023002', customerName: '李四' }
]
} else {
this.contractOptions = []
}
},
//
searchPayees(query) {
if (query) {
// API
this.payeeOptions = [
{ id: '10001', name: '张三', contact: '13812345678' },
{ id: '10002', name: '李四', contact: '13987654321' }
]
} else {
this.payeeOptions = []
}
},
//
handlePayeeChange(payeeId) {
const payee = this.payeeOptions.find(item => item.id === payeeId)
if (payee) {
this.billForm.payeeName = payee.name
this.billForm.payeeContact = payee.contact
//
this.$nextTick(() => {
this.$refs.billInfoForm.validateField('payeeId')
})
}
},
//
handleFeeTypeChange(value) {
if (value) {
//
let selectedFeeType = null;
let selectedCategory = null;
//
for (const category of this.feeTypeOptions) {
//
if (category.id === value) {
selectedCategory = category;
selectedFeeType = {
id: category.id,
name: category.label,
type: '分类'
};
break;
}
//
if (category.children && category.children.length > 0) {
const found = category.children.find(item => item.id === value);
if (found) {
selectedCategory = category;
selectedFeeType = {
id: found.id,
name: found.label,
categoryName: category.label,
type: '费用类型'
};
break;
}
}
}
if (selectedFeeType) {
// IDID
if (selectedFeeType.type === '分类') {
this.billForm.feeCategoryId = selectedFeeType.id;
this.billForm.feeTypeId = '';
this.billForm.feeTypeName = selectedFeeType.name;
} else {
this.billForm.feeCategoryId = selectedCategory.id;
this.billForm.feeTypeId = selectedFeeType.id;
this.billForm.feeTypeName = selectedFeeType.name;
}
}
} else {
//
this.billForm.feeCategoryId = '';
this.billForm.feeTypeId = '';
this.billForm.feeTypeName = '';
}
},
//
handleCompanyChange(companyId) {
const company = this.companyOptions.find(item => item.id === companyId)
if (company) {
this.billForm.companyName = company.name
//
this.$nextTick(() => {
this.$refs.billInfoForm.validateField('companyId')
})
}
//
this.billForm.accountId = ''
this.getAccountOptions(companyId)
},
//
handleBillingPeriodChange(val) {
if (val && val.length === 2) {
this.billForm.billingStartDate = val[0]
this.billForm.billingEndDate = val[1]
} else {
this.billForm.billingStartDate = ''
this.billForm.billingEndDate = ''
}
},
//
getRoomTreeData() {
//
const roomData = [
{
"id": "3",
"projectName": "智慧产业园",
"projectType": "产业园区",
"buildings": []
},
{
"id": "8",
"projectName": "测试-1",
"projectType": "产业园区",
"buildings": [
{
"id": 7,
"buildingName": "4121",
"buildingCode": "1234",
"floors": [
{
"id": 7,
"floorName": "123",
"floorNumber": 11,
"rooms": [
{
"id": 31,
"roomNumber": "504",
"roomType": null,
"roomStatus": "1",
"buildingArea": 122.00,
"rentalArea": 20.99
},
{
"id": 30,
"roomNumber": "504",
"roomType": null,
"roomStatus": "1",
"buildingArea": 122.00,
"rentalArea": 20.99
},
{
"id": 32,
"roomNumber": "505",
"roomType": null,
"roomStatus": "1",
"buildingArea": 122.00,
"rentalArea": 20.99
}
]
}
]
}
]
}
];
//
this.roomTreeData = roomData.map(project => {
return {
id: project.id,
label: project.projectName,
children: project.buildings.map(building => {
return {
id: building.id,
label: building.buildingName,
children: building.floors.map(floor => {
return {
id: floor.id,
label: floor.floorName,
children: floor.rooms.map(room => {
return {
id: room.id,
label: `${room.roomNumber}${room.rentalArea}㎡)`,
isRoom: true,
//
roomData: room
}
})
}
})
}
})
}
});
},
//
handleRoomTreeCheck(node, data) {
//
const checkedNodes = this.$refs.roomTree.getCheckedNodes(true)
//
const roomNodes = checkedNodes.filter(node => node.isRoom)
// ID
this.selectedRoomIds = roomNodes.map(node => node.id)
//
this.billForm.roomInfoList = []
// Map
const projectMap = new Map()
//
roomNodes.forEach(roomNode => {
//
let currentNode = this.$refs.roomTree.getNode(roomNode.id)
let roomInfo = roomNode.roomData
let floorNode, buildingNode, projectNode
//
while (currentNode && currentNode.parent) {
const parent = currentNode.parent
if (parent.level === 3) { //
floorNode = parent.data
} else if (parent.level === 2) { //
buildingNode = parent.data
} else if (parent.level === 1) { //
projectNode = parent.data
}
currentNode = parent
}
//
if (roomInfo && floorNode && buildingNode && projectNode) {
//
this.billForm.roomInfoList.push({
roomId: roomNode.id,
roomNumber: roomInfo.roomNumber || roomNode.label.split('')[0],
buildingId: buildingNode.id,
buildingName: buildingNode.label,
floorId: floorNode.id,
floorName: floorNode.label,
projectId: projectNode.id,
projectName: projectNode.label,
rentArea: roomInfo.rentalArea || 0
})
}
})
},
//
beforeUpload(file) {
const isValidType = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'application/pdf'
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) {
this.attachmentLoading = true
// API
setTimeout(() => {
this.fileList.push({
name: options.file.name,
url: URL.createObjectURL(options.file),
operatorName: '当前用户',
operateTime: new Date().toLocaleString()
})
this.attachmentLoading = false
this.$message.success('上传成功')
}, 1000)
},
//
previewFile(file) {
window.open(file.url)
},
//
downloadFile(file) {
// API
const link = document.createElement('a')
link.href = file.url
link.download = file.name
link.click()
},
//
deleteFile(index) {
this.$confirm('确认删除该附件?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.fileList.splice(index, 1)
this.$message.success('删除成功')
}).catch(() => {})
},
//
getFormData() {
// billFormformExtension
if (this.formExtension.feeTypeSelected) {
// handleFeeTypeChange
}
if (this.formExtension.billingPeriod && this.formExtension.billingPeriod.length === 2) {
this.billForm.billingStartDate = this.formExtension.billingPeriod[0]
this.billForm.billingEndDate = this.formExtension.billingPeriod[1]
}
return this.billForm
},
//
validateForm() {
return new Promise(resolve => {
//
this.$refs.chargeModeForm.validate(valid1 => {
if (!valid1) {
this.activeTab = 'chargeMode'
resolve(false)
return
}
//
this.$refs.formExtension.validate(valid2 => {
if (!valid2) {
this.activeTab = 'billInfo'
resolve(false)
return
}
//
this.$refs.billInfoForm.validate(valid3 => {
if (!valid3) {
this.activeTab = 'billInfo'
resolve(false)
return
}
//
if (this.billForm.roomInfoList.length === 0) {
this.$message.warning('请至少选择一个房源')
this.activeTab = 'roomInfo'
resolve(false)
return
}
resolve(true)
})
})
})
})
},
//
resetForm() {
//
if (this.$refs.chargeModeForm) {
this.$refs.chargeModeForm.resetFields()
}
if (this.$refs.billInfoForm) {
this.$refs.billInfoForm.resetFields()
}
if (this.$refs.formExtension) {
this.$refs.formExtension.resetFields()
}
//
this.activeTab = 'chargeMode'
//
this.billForm = {
contractId: '',
contractNumber: '',
payeeId: '',
payeeName: '',
payeeContact: '',
feeCategoryId: '',
feeTypeId: '',
feeTypeName: '',
currCode: '156', //
billingStartDate: '',
billingEndDate: '',
receivableAmount: 0,
price: 0,
priceU: '元/月',
taxInclusiveRule: '含税',
taxRate: 0,
receivableDate: '',
specialBillType: '正常',
lateFeeStartDays: 0,
lateFeeRate: 0,
lateFeeLimit: 0,
companyId: '',
companyName: '',
accountId: '',
billRemark: '',
roomInfoList: []
}
//
this.formExtension = {
feeTypeSelected: null,
billingPeriod: []
}
//
this.selectedRoomIds = []
if (this.$refs.roomTree) {
this.$refs.roomTree.setCheckedKeys([])
}
//
this.fileList = []
}
}
}
</script>
<style lang="scss" scoped>
.add-bill-container {
padding: 20px;
.upload-area {
margin: 20px 0;
}
.delete-btn {
color: #F56C6C;
}
}
</style>

View File

@ -0,0 +1,256 @@
<template>
<div class="bill-detail-container">
<el-tabs v-model="activeTab">
<el-tab-pane label="账单基本信息" name="basicInfo">
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="账单编号">{{ billDetail.billNumber }}</el-descriptions-item>
<el-descriptions-item label="账单来源">{{ getBillSourceName(billDetail.billSource) }}</el-descriptions-item>
<el-descriptions-item label="账单状态">
<el-tag :type="billDetail.billStatus === '1' ? 'success' : 'info'">
{{ billDetail.billStatus === '1' ? '开启' : '关闭' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="对方名称">{{ billDetail.payeeName }}</el-descriptions-item>
<el-descriptions-item label="支付联系方式">{{ billDetail.payeeContact }}</el-descriptions-item>
<el-descriptions-item label="关联合同">{{ billDetail.contractNumber || '无' }}</el-descriptions-item>
<el-descriptions-item label="费用类型">{{ billDetail.feeTypeName }}</el-descriptions-item>
<el-descriptions-item label="计费周期">
{{ billDetail.billingStartDate }} {{ billDetail.billingEndDate }}
</el-descriptions-item>
<el-descriptions-item label="应收日期">{{ billDetail.receivableDate }}</el-descriptions-item>
<el-descriptions-item label="结清状态">
<el-tag :type="getClearStatusType(billDetail.clearStatus)">
{{ getClearStatusName(billDetail.clearStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="逾期状态">
<el-tag :type="billDetail.overdueStatus === '1' ? 'danger' : 'success'">
{{ billDetail.overdueStatus === '1' ? '逾期' : '正常' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="滞纳金状态">
<el-tag :type="getLateFeeStatusType(billDetail.lateFeeStatus)">
{{ getLateFeeStatusName(billDetail.lateFeeStatus) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="账单金额">{{ formatAmount(billDetail.billAmount) }}</el-descriptions-item>
<el-descriptions-item label="应收金额">{{ formatAmount(billDetail.receivableAmount) }}</el-descriptions-item>
<el-descriptions-item label="实收金额">{{ formatAmount(billDetail.receivedAmount) }}</el-descriptions-item>
<el-descriptions-item label="需收金额">{{ formatAmount(billDetail.needAmount) }}</el-descriptions-item>
<el-descriptions-item label="调整金额">{{ formatAmount(billDetail.adjustAmount) }}</el-descriptions-item>
<el-descriptions-item label="应收滞纳金">{{ formatAmount(billDetail.receivableLateFee) }}</el-descriptions-item>
<el-descriptions-item label="税率">{{ billDetail.taxRate }}%</el-descriptions-item>
<el-descriptions-item label="税额">{{ formatAmount(billDetail.taxAmount) }}</el-descriptions-item>
<el-descriptions-item label="含税规则">{{ billDetail.taxInclusiveRule }}</el-descriptions-item>
<el-descriptions-item label="特殊账单类型">{{ billDetail.specialBillType }}</el-descriptions-item>
<el-descriptions-item label="开据状态">
<el-tag :type="billDetail.receiptStatus === '1' ? 'success' : 'info'">
{{ billDetail.receiptStatus === '1' ? '已开据' : '未开据' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="所属公司">{{ billDetail.companyName }}</el-descriptions-item>
<el-descriptions-item label="租赁数(计租面积)">{{ billDetail.rentArea }} </el-descriptions-item>
<el-descriptions-item label="项目名称">{{ billDetail.projectName }}</el-descriptions-item>
<el-descriptions-item label="账单备注" :span="3">{{ billDetail.billRemark || '无' }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="房源信息" name="roomInfo">
<el-table :data="roomList" border style="width: 100%">
<el-table-column prop="roomNumber" label="房号" min-width="100"></el-table-column>
<el-table-column prop="floorName" label="楼层" min-width="100"></el-table-column>
<el-table-column prop="buildingName" label="楼宇" min-width="120"></el-table-column>
<el-table-column prop="projectName" label="项目" min-width="150"></el-table-column>
<el-table-column prop="rentArea" label="计租面积(㎡)" min-width="120" align="right"></el-table-column>
</el-table>
<div v-if="roomList.length === 0" class="empty-data">
<el-empty description="暂无房源信息"></el-empty>
</div>
</el-tab-pane>
<el-tab-pane label="附件信息" name="attachment">
<el-table :data="attachmentList" border style="width: 100%">
<el-table-column prop="fileName" label="文件名" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="operatorName" label="操作人" width="150"></el-table-column>
<el-table-column prop="operateTime" label="操作时间" width="180"></el-table-column>
<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>
</template>
</el-table-column>
</el-table>
<div v-if="attachmentList.length === 0" class="empty-data">
<el-empty description="暂无附件信息"></el-empty>
</div>
</el-tab-pane>
<el-tab-pane label="操作记录" name="operationLog">
<el-table :data="operationList" border style="width: 100%">
<el-table-column prop="operationType" label="操作类型" width="150"></el-table-column>
<el-table-column prop="operatorName" label="操作人" width="150"></el-table-column>
<el-table-column prop="operateTime" label="操作时间" width="180"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="300" show-overflow-tooltip></el-table-column>
</el-table>
<div v-if="operationList.length === 0" class="empty-data">
<el-empty description="暂无操作记录"></el-empty>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { getBillDetail, getBillAttachmentList } from '@/api/finance'
export default {
name: 'BillDetail',
props: {
billId: {
type: [Number, String],
required: true
}
},
data() {
return {
activeTab: 'basicInfo',
loading: false,
billDetail: {},
roomList: [],
attachmentList: [],
operationList: []
}
},
created() {
this.getBillData()
},
methods: {
//
getBillData() {
this.loading = true
//
getBillDetail(this.billId).then(response => {
this.billDetail = response.data || {}
//
this.roomList = [
{
roomId: 'R001',
roomNumber: this.billDetail.roomNumber || '',
floorName: '1层',
buildingName: '智慧园区A栋',
projectName: this.billDetail.projectName || '',
rentArea: this.billDetail.rentArea || 0
}
]
this.loading = false
}).catch(() => {
this.loading = false
})
//
getBillAttachmentList(this.billId).then(response => {
this.attachmentList = response.data || []
})
//
this.operationList = [
{
operationType: '创建账单',
operatorName: '系统管理员',
operateTime: '2023-06-20 10:30:00',
remark: '创建新账单'
}
]
},
//
formatAmount(amount) {
if (amount === undefined || amount === null) {
return '0.00'
}
return parseFloat(amount).toFixed(2) + ' 元'
},
//
getBillSourceName(source) {
const sourceMap = {
'1': '合同账单',
'2': '收费标准账单',
'3': '自建全部账单',
'4': '自建关联合同账单',
'5': '自建未关联合同账单'
}
return sourceMap[source] || '未知来源'
},
//
getClearStatusType(status) {
const statusMap = {
'0': 'info',
'1': 'success',
'2': 'warning',
'3': 'danger',
'5': 'primary',
'6': 'info',
'7': 'success'
}
return statusMap[status] || 'info'
},
//
getClearStatusName(status) {
const statusMap = {
'0': '未付款',
'1': '已结清',
'2': '部分结清',
'3': '待退款',
'5': '待收款',
'6': '对账确认中',
'7': '已付款'
}
return statusMap[status] || '未知状态'
},
//
getLateFeeStatusType(status) {
const statusMap = {
'0': 'info',
'1': 'warning',
'2': 'success'
}
return statusMap[status] || 'info'
},
//
getLateFeeStatusName(status) {
const statusMap = {
'0': '无滞纳金',
'1': '有滞纳金',
'2': '滞纳金免除'
}
return statusMap[status] || '未知状态'
},
//
previewFile(file) {
window.open(file.storePath)
},
//
downloadFile(file) {
// API
const link = document.createElement('a')
link.href = file.storePath
link.download = file.fileName
link.click()
}
}
}
</script>
<style lang="scss" scoped>
.bill-detail-container {
padding: 10px;
.empty-data {
margin: 20px 0;
display: flex;
justify-content: center;
}
}
</style>

View File

@ -0,0 +1,409 @@
<template>
<div class="app-container">
<!-- 搜索表单 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" class="search-form">
<el-form-item label="合同编号" prop="contractNumber">
<el-input v-model="queryParams.contractNumber" placeholder="请输入合同编号" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="房号" prop="roomNumber">
<el-input v-model="queryParams.roomNumber" placeholder="请输入房号" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="对方名称" prop="payeeName">
<el-input v-model="queryParams.payeeName" placeholder="请输入对方名称" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="账单编号" prop="billNumber">
<el-input v-model="queryParams.billNumber" placeholder="请输入账单编号" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="账单状态" prop="billStatus">
<el-select v-model="queryParams.billStatus" placeholder="请选择账单状态" clearable multiple style="width: 220px">
<el-option label="开启" value="1" />
<el-option label="关闭" value="0" />
</el-select>
</el-form-item>
<el-form-item label="应收时间" prop="receivableDate">
<el-date-picker
v-model="dateRange"
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="clearStatus">
<el-select v-model="queryParams.clearStatus" placeholder="请选择结清状态" clearable multiple style="width: 220px">
<el-option label="未付款" value="0" />
<el-option label="已结清" value="1" />
<el-option label="部分结清" value="2" />
<el-option label="待退款" value="3" />
<el-option label="待收款" value="5" />
<el-option label="对账确认中" value="6" />
<el-option label="已付款" value="7" />
</el-select>
</el-form-item>
<el-form-item label="账单来源" prop="billSource">
<el-select v-model="queryParams.billSource" placeholder="请选择账单来源" clearable style="width: 220px">
<el-option label="合同账单" value="1" />
<el-option label="收费标准账单" value="2" />
<el-option label="自建全部账单" value="3" />
<el-option label="自建关联合同账单" value="4" />
<el-option label="自建未关联合同账单" value="5" />
</el-select>
</el-form-item>
<el-form-item label="费用类型" prop="feeType">
<el-cascader
v-model="feeTypeSelected"
:options="feeTypeOptions"
:props="{
label: 'label',
value: 'id',
children: 'children',
emitPath: false,
expandTrigger: 'hover'
}"
clearable
placeholder="请选择费用类型"
style="width: 220px">
</el-cascader>
</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="success" icon="el-icon-plus" style="float: right;" @click="handleAdd">添加收款账单</el-button>
<el-button type="warning" icon="el-icon-download" style="float: right; margin-right: 10px;" @click="handleExport">导出</el-button>
</el-form>
<!-- 表格区域 -->
<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="projectName" min-width="120" 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="contractNumber" min-width="150" show-overflow-tooltip />
<el-table-column label="结清状态" min-width="100" align="center">
<template slot-scope="scope">
<el-tag :type="getClearStatusType(scope.row.clearStatus)">
{{ getClearStatusName(scope.row.clearStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="费用类型" prop="feeTypeName" min-width="120" show-overflow-tooltip />
<el-table-column label="账单金额" prop="billAmount" min-width="100" align="right">
<template slot-scope="scope">
{{ scope.row.billAmount ? scope.row.billAmount.toFixed(2) : '0.00' }}
</template>
</el-table-column>
<el-table-column label="应收金额" prop="receivableAmount" min-width="100" align="right">
<template slot-scope="scope">
{{ scope.row.receivableAmount ? scope.row.receivableAmount.toFixed(2) : '0.00' }}
</template>
</el-table-column>
<el-table-column label="实收金额" prop="receivedAmount" min-width="100" align="right">
<template slot-scope="scope">
{{ scope.row.receivedAmount ? scope.row.receivedAmount.toFixed(2) : '0.00' }}
</template>
</el-table-column>
<el-table-column label="开始日期" prop="billingStartDate" min-width="120" align="center" />
<el-table-column label="结束日期" prop="billingEndDate" min-width="120" align="center" />
<el-table-column label="应收日期" prop="receivableDate" min-width="120" align="center" />
<el-table-column label="操作" width="150" 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="dialogVisible" width="80%" append-to-body @closed="handleDialogClosed">
<add-bill ref="addBillForm" />
<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="submitAddBill"> </el-button>
</div>
</el-dialog>
<!-- 账单详情对话框 -->
<el-dialog title="账单详情" :visible.sync="detailVisible" width="80%" append-to-body>
<bill-detail v-if="detailVisible" :bill-id="currentBill.id" />
</el-dialog>
</div>
</template>
<script>
import { getFeeTypeTree, getBillList, exportBillList, addBill, getBillDetail } from '@/api/finance'
import AddBill from './components/AddBill'
import BillDetail from './components/BillDetail'
export default {
name: 'BillList',
components: {
AddBill,
BillDetail
},
data() {
return {
//
loading: false,
//
billList: [],
//
total: 0,
//
dateRange: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
contractNumber: '',
roomNumber: '',
payeeName: '',
billNumber: '',
billStatus: [],
receivableDateStart: '',
receivableDateEnd: '',
clearStatus: [],
billSource: '',
feeTypeId: undefined,
feeCategoryId: undefined
},
//
feeTypeSelected: undefined,
//
feeTypeOptions: [],
//
dialogVisible: false,
detailVisible: false,
//
currentBill: {}
}
},
created() {
this.getList()
this.getFeeTypeOptions()
},
methods: {
//
getList() {
this.loading = true
//
const queryParams = { ...this.queryParams }
//
if (this.dateRange && this.dateRange.length > 0) {
queryParams.receivableDateStart = this.dateRange[0]
queryParams.receivableDateEnd = this.dateRange[1]
}
//
if (this.feeTypeSelected) {
queryParams.feeTypeId = this.feeTypeSelected
}
getBillList(queryParams).then(response => {
this.billList = response.data.data || []
this.total = response.data.total
this.loading = false
}).catch(() => {
this.loading = false
})
},
//
getFeeTypeOptions() {
getFeeTypeTree({ level: 2 }).then(response => {
if (response.code === '0000000000000000') {
//
this.feeTypeOptions = this.processTreeData(response.data)
}
})
},
//
processTreeData(data) {
if (!data || !Array.isArray(data) || data.length === 0) {
console.warn('无费用类型数据')
return []
}
return data.map(category => {
// id
const categoryId = category.id || Math.random().toString(36).substr(2, 9)
//
let children = []
if (category.financeFeeTypes && Array.isArray(category.financeFeeTypes)) {
children = category.financeFeeTypes.map(feeType => {
return {
id: feeType.id || Math.random().toString(36).substr(2, 9),
label: feeType.feeTypeName || '未命名费用',
categoryId: categoryId,
// 便使
feeTypeName: feeType.feeTypeName || '未命名费用',
//
children: null
}
})
}
return {
id: categoryId,
label: category.categoryName || '未命名分类',
categoryName: category.categoryName || '未命名分类',
children: children.length > 0 ? children : null
}
}).filter(item => item.children && item.children.length > 0)
},
//
handleQuery() {
this.queryParams.pageNum = 1
this.getList()
},
//
resetQuery() {
this.dateRange = []
this.feeTypeSelected = undefined
this.$refs.queryForm.resetFields()
this.handleQuery()
},
//
handleSizeChange(val) {
this.queryParams.pageSize = val
this.getList()
},
//
handleCurrentChange(val) {
this.queryParams.pageNum = val
this.getList()
},
//
handleAdd() {
this.dialogVisible = true
},
//
handleDialogClosed() {
this.$refs.addBillForm.resetForm()
},
//
handleViewDetail(row) {
this.currentBill = row
this.detailVisible = true
},
//
handleExport() {
//
const exportParams = { ...this.queryParams }
delete exportParams.pageNum
delete exportParams.pageSize
//
if (this.dateRange && this.dateRange.length > 0) {
exportParams.receivableDateStart = this.dateRange[0]
exportParams.receivableDateEnd = this.dateRange[1]
}
//
if (this.feeTypeSelected) {
exportParams.feeTypeId = this.feeTypeSelected
}
this.$confirm('是否确认导出所有数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
exportBillList(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)
}
})
})
},
//
submitAddBill() {
//
this.$refs.addBillForm.validateForm().then(valid => {
if (valid) {
const billData = this.$refs.addBillForm.getFormData()
addBill(billData).then(response => {
this.$message.success('添加账单成功')
this.dialogVisible = false
this.getList()
}).catch(() => {
//
})
}
})
},
//
getClearStatusType(status) {
const statusMap = {
'0': 'info',
'1': 'success',
'2': 'warning',
'3': 'danger',
'5': 'primary',
'6': 'info',
'7': 'success'
}
return statusMap[status] || 'info'
},
//
getClearStatusName(status) {
const statusMap = {
'0': '未付款',
'1': '已结清',
'2': '部分结清',
'3': '待退款',
'5': '待收款',
'6': '对账确认中',
'7': '已付款'
}
return statusMap[status] || '未知状态'
}
}
}
</script>
<style lang="scss" scoped>
.app-container {
padding: 20px;
.search-form {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -50,7 +50,7 @@
<div class="fee-type-search"> <div class="fee-type-search">
<el-form :inline="true" :model="queryParams" class="search-form"> <el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="费用名称"> <el-form-item label="费用名称">
<el-input v-model="queryParams.feeTypeName" placeholder="请输入费用名称" clearable <el-input v-model="queryParams.feTpName" placeholder="请输入费用名称" clearable
@keyup.enter.native="handleQuery"></el-input> @keyup.enter.native="handleQuery"></el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@ -62,7 +62,7 @@
<!-- 费用类型表格 --> <!-- 费用类型表格 -->
<el-table v-loading="loading" :data="typeList" border> <el-table v-loading="loading" :data="typeList" border>
<el-table-column type="index" width="50" align="center" label="序号"></el-table-column> <el-table-column type="index" width="50" align="center" label="序号"></el-table-column>
<el-table-column prop="feeTypeName" label="费用名称" min-width="140"></el-table-column> <el-table-column prop="feTpName" label="费用名称" min-width="140"></el-table-column>
<el-table-column label="费用分类" min-width="120" align="center"> <el-table-column label="费用分类" min-width="120" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
{{ getCategoryNameById(scope.row.categoryId) }} {{ getCategoryNameById(scope.row.categoryId) }}
@ -129,8 +129,8 @@
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="费用类型名称" prop="feeTypeName"> <el-form-item label="费用类型名称" prop="feTpName">
<el-input v-model="typeForm.feeTypeName" placeholder="请输入费用类型名称" :disabled="typeEdit" <el-input v-model="typeForm.feTpName" placeholder="请输入费用类型名称" :disabled="typeEdit"
style="width: 100%"></el-input> style="width: 100%"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
@ -186,7 +186,7 @@ export default {
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
feeTypeName: undefined, feTpName: undefined,
categoryId: null categoryId: null
}, },
@ -216,13 +216,13 @@ export default {
typeEdit: false, typeEdit: false,
typeForm: { typeForm: {
id: undefined, id: undefined,
feeTypeName: '', feTpName: '',
categoryId: null, categoryId: null,
status: '1', status: '1',
defauVerFlag: '0' defauVerFlag: '0'
}, },
typeRules: { typeRules: {
feeTypeName: [ feTpName: [
{ required: true, message: '费用类型名称不能为空', trigger: 'blur' }, { required: true, message: '费用类型名称不能为空', trigger: 'blur' },
{ min: 2, max: 50, message: '费用类型名称长度必须在2-50个字符之间', trigger: 'blur' } { min: 2, max: 50, message: '费用类型名称长度必须在2-50个字符之间', trigger: 'blur' }
], ],
@ -296,7 +296,7 @@ export default {
}, },
// //
resetQuery() { resetQuery() {
this.queryParams.feeTypeName = undefined this.queryParams.feTpName = undefined
this.handleQuery() this.handleQuery()
}, },
// //
@ -411,7 +411,7 @@ export default {
// //
this.typeForm = { this.typeForm = {
id: undefined, id: undefined,
feeTypeName: '', feTpName: '',
categoryId: this.selectedCategoryId ? parseInt(this.selectedCategoryId) : null, categoryId: this.selectedCategoryId ? parseInt(this.selectedCategoryId) : null,
status: '1', status: '1',
defauVerFlag: '0' defauVerFlag: '0'
@ -455,7 +455,7 @@ export default {
// //
const submitData = { const submitData = {
id: this.typeForm.id, id: this.typeForm.id,
feeTypeName: this.typeForm.feeTypeName, feTpName: this.typeForm.feTpName,
categoryId: this.typeForm.categoryId, categoryId: this.typeForm.categoryId,
status: this.typeForm.status status: this.typeForm.status
} }

View File

@ -4,7 +4,7 @@
<!-- 搜索栏 --> <!-- 搜索栏 -->
<div class="filter-container"> <div class="filter-container">
<el-input v-model="queryParams.payeeNameUnitName" placeholder="请输入收款方单位名称" clearable style="width: 250px;" class="filter-item" /> <el-input v-model="queryParams.payeeUnitName" placeholder="请输入收款方单位名称" clearable style="width: 250px;" class="filter-item" />
<el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button> <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
<el-button @click="resetQuery">重置</el-button> <el-button @click="resetQuery">重置</el-button>
<el-button type="primary" icon="el-icon-plus" style="float: right;" @click="handleAdd">新增收款方信息</el-button> <el-button type="primary" icon="el-icon-plus" style="float: right;" @click="handleAdd">新增收款方信息</el-button>
@ -17,10 +17,10 @@
{{ formatCompanyName(scope.row.companyId) }} {{ formatCompanyName(scope.row.companyId) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="payeeNameUnitName" label="收款方单位名称" /> <el-table-column prop="payeeUnitName" label="收款方单位名称" />
<el-table-column prop="payeeName" label="收款人" /> <el-table-column prop="payeeName" label="收款人" />
<el-table-column prop="addr" label="地址" /> <el-table-column prop="payeeAddr" label="地址" />
<el-table-column prop="contTel" label="电话" /> <el-table-column prop="payeePhone" label="电话" />
<el-table-column prop="buildingIds" label="应用楼宇"> <el-table-column prop="buildingIds" label="应用楼宇">
<template slot-scope="scope"> <template slot-scope="scope">
{{ formatBuildingNames(scope.row.buildingIds) }} {{ formatBuildingNames(scope.row.buildingIds) }}
@ -53,17 +53,17 @@
:value="item.id" /> :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="收款方单位名称" prop="payeeNameUnitName"> <el-form-item label="收款方单位名称" prop="payeeUnitName">
<el-input v-model="form.payeeNameUnitName" placeholder="请输入收款方单位名称" /> <el-input v-model="form.payeeUnitName" placeholder="请输入收款方单位名称" />
</el-form-item> </el-form-item>
<el-form-item label="收款人" prop="payeeName"> <el-form-item label="收款人" prop="payeeName">
<el-input v-model="form.payeeName" placeholder="请输入收款人" /> <el-input v-model="form.payeeName" placeholder="请输入收款人" />
</el-form-item> </el-form-item>
<el-form-item label="地址" prop="addr"> <el-form-item label="地址" prop="payeeAddr">
<el-input v-model="form.addr" placeholder="请输入地址" /> <el-input v-model="form.payeeAddr" placeholder="请输入地址" />
</el-form-item> </el-form-item>
<el-form-item label="电话" prop="contTel"> <el-form-item label="电话" prop="payeePhone">
<el-input v-model="form.contTel" placeholder="请输入电话" /> <el-input v-model="form.payeePhone" placeholder="请输入电话" />
</el-form-item> </el-form-item>
<el-form-item label="应用楼宇" prop="buildingIds"> <el-form-item label="应用楼宇" prop="buildingIds">
<el-popover <el-popover
@ -127,7 +127,7 @@ export default {
queryParams: { queryParams: {
current: 1, current: 1,
size: 10, size: 10,
payeeNameUnitName: '' payeeUnitName: ''
}, },
// //
@ -140,10 +140,10 @@ export default {
// //
payeeInfo: { payeeInfo: {
companyId: '', companyId: '',
payeeNameUnitName: '', payeeUnitName: '',
payeeName: '', payeeName: '',
addr: '', payeeAddr: '',
contTel: '', payeePhone: '',
buildingIds: [] buildingIds: []
}, },
@ -174,10 +174,10 @@ export default {
// //
form: { form: {
companyId: '', companyId: '',
payeeNameUnitName: '', payeeUnitName: '',
payeeName: '', payeeName: '',
addr: '', payeeAddr: '',
contTel: '', payeePhone: '',
buildingIds: [], buildingIds: [],
buildingNames: '' buildingNames: ''
}, },
@ -187,13 +187,13 @@ export default {
companyId: [ companyId: [
{ required: true, message: '请选择关联公司', trigger: 'change' } { required: true, message: '请选择关联公司', trigger: 'change' }
], ],
payeeNameUnitName: [ payeeUnitName: [
{ required: true, message: '请输入收款方单位名称', trigger: 'blur' } { required: true, message: '请输入收款方单位名称', trigger: 'blur' }
], ],
payeeName: [ payeeName: [
{ required: true, message: '请输入收款人', trigger: 'blur' } { required: true, message: '请输入收款人', trigger: 'blur' }
], ],
contTel: [ payeePhone: [
{ pattern: /^(\d{3,4}-\d{7,8}(-\d{1,4})?|1[3-9]\d{9})$/, message: '电话格式不正确', trigger: 'blur' } { pattern: /^(\d{3,4}-\d{7,8}(-\d{1,4})?|1[3-9]\d{9})$/, message: '电话格式不正确', trigger: 'blur' }
], ],
buildingIds: [ buildingIds: [
@ -222,10 +222,10 @@ export default {
this.payeeList = [{ this.payeeList = [{
id: 1, id: 1,
companyId: 'COM001', companyId: 'COM001',
payeeNameUnitName: '智慧园区管理有限公司', payeeUnitName: '智慧园区管理有限公司',
payeeName: '张三', payeeName: '张三',
addr: '北京市海淀区中关村大街1号', payeeAddr: '北京市海淀区中关村大街1号',
contTel: '13800138000', payeePhone: '13800138000',
buildingIds: 'BLD001,BLD002' buildingIds: 'BLD001,BLD002'
}] }]
this.total = 1 this.total = 1
@ -239,10 +239,10 @@ export default {
this.payeeList = [{ this.payeeList = [{
id: 1, id: 1,
companyId: 'COM001', companyId: 'COM001',
payeeNameUnitName: '智慧园区管理有限公司', payeeUnitName: '智慧园区管理有限公司',
payeeName: '张三', payeeName: '张三',
addr: '北京市海淀区中关村大街1号', payeeAddr: '北京市海淀区中关村大街1号',
contTel: '13800138000', payeePhone: '13800138000',
buildingIds: 'BLD001,BLD002' buildingIds: 'BLD001,BLD002'
}] }]
this.total = 1 this.total = 1
@ -348,7 +348,7 @@ export default {
this.queryParams = { this.queryParams = {
current: 1, current: 1,
size: 10, size: 10,
payeeNameUnitName: '' payeeUnitName: ''
} }
this.getPayeeList() this.getPayeeList()
}, },
@ -371,10 +371,10 @@ export default {
this.currentId = null this.currentId = null
this.form = { this.form = {
companyId: '', companyId: '',
payeeNameUnitName: '', payeeUnitName: '',
payeeName: '', payeeName: '',
addr: '', payeeAddr: '',
contTel: '', payeePhone: '',
buildingIds: [], buildingIds: [],
buildingNames: '' buildingNames: ''
} }

View File

@ -10,14 +10,14 @@ module.exports = {
warnings: false, warnings: false,
errors: true errors: true
}, },
proxy: { // proxy: {
'/': { // '/': {
target: 'http://192.168.137.214:8082/api', // target: 'http://192.168.137.214:8082',
changeOrigin: true, // changeOrigin: true,
pathRewrite: { // pathRewrite: {
'^/api': '/api' // '^/api': '/api'
} // }
} // }
} // }
} }
} }