<template>
|
<div class="app-container">
|
<div class="search_form">
|
<el-form :model="searchForm" :inline="true">
|
<el-form-item label="办事处:">
|
<el-input v-model="searchForm.office" placeholder="请输入" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="使用人:">
|
<el-input v-model="searchForm.userName" placeholder="请输入" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="承租人:">
|
<el-input v-model="searchForm.tenant" placeholder="请输入" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="合同甲方:">
|
<el-input v-model="searchForm.partyA" placeholder="请输入" clearable prefix-icon="Search" @keyup.enter="handleQuery" />
|
</el-form-item>
|
<el-form-item label="预警状态:">
|
<el-select v-model="searchForm.warnStatus" placeholder="请选择" clearable style="width: 140px">
|
<el-option label="正常" value="normal" />
|
<el-option label="即将到期" value="expiring" />
|
<el-option label="已逾期" value="overdue" />
|
</el-select>
|
</el-form-item>
|
<el-form-item>
|
<el-button type="primary" @click="handleQuery">搜索</el-button>
|
<el-button @click="resetSearch">重置</el-button>
|
</el-form-item>
|
</el-form>
|
</div>
|
|
<div class="table_list">
|
<div class="actions">
|
<div></div>
|
<div>
|
<el-button type="primary" @click="openForm('add')">新增租赁</el-button>
|
<el-button type="primary" plain @click="handleExport">导出</el-button>
|
<el-button type="danger" plain @click="handleDelete">删除</el-button>
|
</div>
|
</div>
|
<el-table
|
:data="tableData"
|
border
|
v-loading="tableLoading"
|
@selection-change="handleSelectionChange"
|
style="width: 100%"
|
height="calc(100vh - 18.5em)"
|
>
|
<el-table-column align="center" type="selection" width="55" fixed="left" />
|
<el-table-column align="center" label="序号" type="index" width="60" fixed="left" />
|
<el-table-column label="办事处" prop="office" width="120" show-overflow-tooltip />
|
<el-table-column label="使用人" prop="userName" width="100" show-overflow-tooltip />
|
<el-table-column label="驻省人员" prop="residentStaff" width="100" show-overflow-tooltip />
|
<el-table-column label="地址" prop="address" min-width="180" show-overflow-tooltip />
|
<el-table-column label="起租时间" prop="rentStartDate" width="110" show-overflow-tooltip />
|
<el-table-column label="合同起租日" prop="contractStartDate" width="110" show-overflow-tooltip />
|
<el-table-column label="合同到期日" prop="contractEndDate" width="120" align="center">
|
<template #default="scope">
|
<span :class="{ 'warn-date': isExpiring(scope.row), 'overdue-date': isOverdue(scope.row) }">
|
{{ scope.row.contractEndDate || "-" }}
|
</span>
|
</template>
|
</el-table-column>
|
<el-table-column label="承租人" prop="tenant" width="100" show-overflow-tooltip />
|
<el-table-column label="合同甲方" prop="partyA" width="120" show-overflow-tooltip />
|
<el-table-column label="电话" prop="phone" width="120" show-overflow-tooltip />
|
<el-table-column label="面积(㎡)" prop="area" width="100" align="right" />
|
<el-table-column label="支付方式" prop="paymentMethod" width="100" align="center" />
|
<el-table-column label="押金(元)" prop="deposit" width="110" align="right" :formatter="formatAmountColumn" />
|
<el-table-column label="租金(元/月)" prop="rent" width="110" align="right" :formatter="formatAmountColumn" />
|
<el-table-column label="预警状态" width="120" align="center">
|
<template #default="scope">
|
<el-tag :type="getWarnStatus(scope.row).type">{{ getWarnStatus(scope.row).text }}</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column fixed="right" label="操作" width="150" align="center">
|
<template #default="scope">
|
<el-button link type="primary" @click="openForm('view', scope.row)">详情</el-button>
|
<el-button link type="primary" @click="openForm('edit', scope.row)">编辑</el-button>
|
<el-button link type="danger" @click="handleRowDelete(scope.row)">删除</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
<pagination
|
v-show="total > 0"
|
:total="total"
|
layout="total, sizes, prev, pager, next, jumper"
|
:page="page.current"
|
:limit="page.size"
|
@pagination="paginationChange"
|
/>
|
</div>
|
|
<!-- 新增/编辑/详情弹窗 -->
|
<FormDialog
|
v-model="dialogVisible"
|
:title="
|
operationType === 'add'
|
? '新增租赁'
|
: operationType === 'edit'
|
? '编辑租赁'
|
: '租赁详情'
|
"
|
:width="'70%'"
|
:operation-type="operationType"
|
@close="closeDialog"
|
@confirm="submitForm"
|
@cancel="closeDialog"
|
>
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="140px" label-position="top">
|
<el-divider content-position="left">租赁信息</el-divider>
|
<el-row :gutter="30">
|
<el-col :span="8">
|
<el-form-item label="办事处:" prop="office">
|
<el-input v-model="form.office" placeholder="请输入办事处" clearable :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="使用人:" prop="userName">
|
<el-input v-model="form.userName" placeholder="请输入使用人" clearable :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="驻省人员:" prop="residentStaff">
|
<el-input v-model="form.residentStaff" placeholder="请输入驻省人员" clearable :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="16">
|
<el-form-item label="地址:" prop="address">
|
<el-input v-model="form.address" placeholder="请输入地址" clearable :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="起租时间:" prop="rentStartDate">
|
<el-date-picker
|
v-model="form.rentStartDate"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="date"
|
placeholder="请选择"
|
clearable
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
/>
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-divider content-position="left">合同信息</el-divider>
|
<el-row :gutter="30">
|
<el-col :span="8">
|
<el-form-item label="合同起租日:" prop="contractStartDate">
|
<el-date-picker
|
v-model="form.contractStartDate"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="date"
|
placeholder="请选择"
|
clearable
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
@change="generateCostMonths"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="合同到期日:" prop="contractEndDate">
|
<el-date-picker
|
v-model="form.contractEndDate"
|
value-format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
type="date"
|
placeholder="请选择"
|
clearable
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
@change="generateCostMonths"
|
/>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="承租人:" prop="tenant">
|
<el-input v-model="form.tenant" placeholder="请输入承租人" clearable :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="8">
|
<el-form-item label="合同甲方:" prop="partyA">
|
<el-input v-model="form.partyA" placeholder="请输入合同甲方" clearable :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="电话:">
|
<el-input v-model="form.phone" placeholder="请输入电话" clearable :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="面积(㎡):">
|
<el-input-number v-model="form.area" :min="0" :precision="2" :step="1" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
<el-row :gutter="30">
|
<el-col :span="8">
|
<el-form-item label="支付方式:" prop="paymentMethod">
|
<el-select v-model="form.paymentMethod" placeholder="请选择" clearable style="width: 100%" :disabled="operationType === 'view'">
|
<el-option v-for="item in paymentMethodOptions" :key="item.value" :label="item.label" :value="item.value" />
|
</el-select>
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="押金(元):">
|
<el-input-number v-model="form.deposit" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" />
|
</el-form-item>
|
</el-col>
|
<el-col :span="8">
|
<el-form-item label="租金(元/月):" prop="rent">
|
<el-input-number v-model="form.rent" :min="0" :precision="2" :step="0.01" controls-position="right" style="width: 100%" :disabled="operationType === 'view'" @change="syncRentToCost" />
|
</el-form-item>
|
</el-col>
|
</el-row>
|
|
<el-divider content-position="left">按月归集费用(房租 + 水电 + 物业 + 快递 + 宽带)</el-divider>
|
<el-table :data="costList" border size="small" style="width: 100%" max-height="360">
|
<el-table-column label="月份" prop="month" width="110" align="center" />
|
<el-table-column label="房租(元)" width="120" align="center">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.rent"
|
:min="0"
|
:precision="2"
|
:step="0.01"
|
controls-position="right"
|
size="small"
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="水电(元)" width="120" align="center">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.waterElectricity"
|
:min="0"
|
:precision="2"
|
:step="0.01"
|
controls-position="right"
|
size="small"
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="物业(元)" width="120" align="center">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.property"
|
:min="0"
|
:precision="2"
|
:step="0.01"
|
controls-position="right"
|
size="small"
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="快递(元)" width="120" align="center">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.express"
|
:min="0"
|
:precision="2"
|
:step="0.01"
|
controls-position="right"
|
size="small"
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="宽带(元)" width="120" align="center">
|
<template #default="scope">
|
<el-input-number
|
v-model="scope.row.broadband"
|
:min="0"
|
:precision="2"
|
:step="0.01"
|
controls-position="right"
|
size="small"
|
style="width: 100%"
|
:disabled="operationType === 'view'"
|
/>
|
</template>
|
</el-table-column>
|
<el-table-column label="月合计(元)" width="110" align="right">
|
<template #default="scope">{{ formatAmount(calcRowTotal(scope.row)) }}</template>
|
</el-table-column>
|
</el-table>
|
<div class="cost-summary">
|
<span>累计:房租 {{ formatAmount(sumCost('rent')) }} 元</span>
|
<span>水电 {{ formatAmount(sumCost('waterElectricity')) }} 元</span>
|
<span>物业 {{ formatAmount(sumCost('property')) }} 元</span>
|
<span>快递 {{ formatAmount(sumCost('express')) }} 元</span>
|
<span>宽带 {{ formatAmount(sumCost('broadband')) }} 元</span>
|
<span class="cost-total">合计 {{ formatAmount(sumCostTotal()) }} 元</span>
|
</div>
|
</el-form>
|
</FormDialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
|
import { ElMessageBox } from "element-plus";
|
import dayjs from "dayjs";
|
import pagination from "@/components/PIMTable/Pagination.vue";
|
import FormDialog from "@/components/Dialog/FormDialog.vue";
|
import {
|
houseLeaseListPage,
|
getHouseLeaseDetail,
|
addOrUpdateHouseLease,
|
delHouseLease,
|
} from "@/api/collaborativeApproval/houseLeaseManagement.js";
|
|
const { proxy } = getCurrentInstance();
|
|
// 支付方式选项
|
const paymentMethodOptions = [
|
{ label: "现金", value: "现金" },
|
{ label: "银行转账", value: "银行转账" },
|
{ label: "支付宝", value: "支付宝" },
|
{ label: "微信", value: "微信" },
|
];
|
|
const tableData = ref([]);
|
const tableLoading = ref(false);
|
const total = ref(0);
|
const selectedRows = ref([]);
|
const page = reactive({ current: 1, size: 10 });
|
|
const searchForm = reactive({
|
office: "",
|
userName: "",
|
tenant: "",
|
partyA: "",
|
warnStatus: "",
|
});
|
|
const operationType = ref("");
|
const dialogVisible = ref(false);
|
const currentId = ref("");
|
const formRef = ref();
|
const costList = ref([]);
|
|
const form = reactive({
|
office: "",
|
userName: "",
|
residentStaff: "",
|
address: "",
|
rentStartDate: "",
|
contractStartDate: "",
|
contractEndDate: "",
|
tenant: "",
|
partyA: "",
|
phone: "",
|
area: undefined,
|
paymentMethod: "",
|
deposit: undefined,
|
rent: undefined,
|
});
|
|
const rules = {
|
office: [{ required: true, message: "请输入办事处", trigger: "blur" }],
|
userName: [{ required: true, message: "请输入使用人", trigger: "blur" }],
|
residentStaff: [{ required: true, message: "请输入驻省人员", trigger: "blur" }],
|
address: [{ required: true, message: "请输入地址", trigger: "blur" }],
|
rentStartDate: [{ required: true, message: "请选择起租时间", trigger: "change" }],
|
contractStartDate: [{ required: true, message: "请选择合同起租日", trigger: "change" }],
|
contractEndDate: [{ required: true, message: "请选择合同到期日", trigger: "change" }],
|
tenant: [{ required: true, message: "请输入承租人", trigger: "blur" }],
|
partyA: [{ required: true, message: "请输入合同甲方", trigger: "blur" }],
|
paymentMethod: [{ required: true, message: "请选择支付方式", trigger: "change" }],
|
rent: [{ required: true, message: "请输入租金", trigger: "change" }],
|
};
|
|
const formatAmount = (val) => {
|
if (val === undefined || val === null || val === "") return "0.00";
|
const num = Number(val);
|
return Number.isNaN(num) ? "0.00" : num.toFixed(2);
|
};
|
const formatAmountColumn = (row, column, cellValue) => formatAmount(cellValue);
|
|
// 按月归集:根据合同起租日到合同到期日生成月份行
|
const generateCostMonths = () => {
|
if (!form.contractStartDate || !form.contractEndDate) return;
|
const start = dayjs(form.contractStartDate);
|
const end = dayjs(form.contractEndDate);
|
if (end.isBefore(start)) return;
|
|
const months = [];
|
let cursor = start.startOf("month");
|
while (cursor.isBefore(end) || cursor.isSame(end, "month")) {
|
months.push(cursor.format("YYYY-MM"));
|
cursor = cursor.add(1, "month");
|
}
|
|
// 保留已填写的数据,仅同步月份范围
|
const oldMap = {};
|
costList.value.forEach((item) => {
|
oldMap[item.month] = item;
|
});
|
|
costList.value = months.map((m) => {
|
const old = oldMap[m];
|
return {
|
month: m,
|
rent: old && old.rent !== undefined && old.rent !== null ? old.rent : form.rent,
|
waterElectricity: old?.waterElectricity ?? undefined,
|
property: old?.property ?? undefined,
|
express: old?.express ?? undefined,
|
broadband: old?.broadband ?? undefined,
|
};
|
});
|
};
|
|
// 修改租金时同步到各月的房租(若该月房租为空或等于旧值)
|
const syncRentToCost = () => {
|
costList.value.forEach((item) => {
|
if (item.rent === undefined || item.rent === null || item.rent === "") {
|
item.rent = form.rent;
|
}
|
});
|
};
|
|
const calcRowTotal = (row) =>
|
(Number(row.rent) || 0) +
|
(Number(row.waterElectricity) || 0) +
|
(Number(row.property) || 0) +
|
(Number(row.express) || 0) +
|
(Number(row.broadband) || 0);
|
|
const sumCost = (key) => costList.value.reduce((acc, item) => acc + (Number(item[key]) || 0), 0);
|
const sumCostTotal = () => costList.value.reduce((acc, item) => acc + calcRowTotal(item), 0);
|
|
// 剩余天数(合同到期日 - 今天)
|
const getRemainingDays = (row) => {
|
if (!row.contractEndDate) return null;
|
return dayjs(row.contractEndDate).startOf("day").diff(dayjs().startOf("day"), "day");
|
};
|
|
const isOverdue = (row) => {
|
const days = getRemainingDays(row);
|
return days !== null && days < 0;
|
};
|
|
const isExpiring = (row) => {
|
const days = getRemainingDays(row);
|
return days !== null && days >= 0 && days <= 30;
|
};
|
|
// 预警状态:正常 / 即将到期(30天内)/ 已逾期
|
const getWarnStatus = (row) => {
|
const days = getRemainingDays(row);
|
if (days === null) return { text: "-", type: "info" };
|
if (days < 0) return { text: `已逾期${Math.abs(days)}天`, type: "danger" };
|
if (days <= 30) return { text: `即将到期(${days}天)`, type: "warning" };
|
return { text: "正常", type: "success" };
|
};
|
|
const resetSearch = () => {
|
searchForm.office = "";
|
searchForm.userName = "";
|
searchForm.tenant = "";
|
searchForm.partyA = "";
|
searchForm.warnStatus = "";
|
page.current = 1;
|
getList();
|
};
|
|
const handleQuery = () => {
|
page.current = 1;
|
getList();
|
};
|
|
const paginationChange = (obj) => {
|
page.current = obj.page;
|
page.size = obj.limit;
|
getList();
|
};
|
|
const getList = () => {
|
tableLoading.value = true;
|
const params = { ...searchForm, current: page.current, size: page.size };
|
return houseLeaseListPage(params)
|
.then((res) => {
|
tableLoading.value = false;
|
tableData.value = res.records || res.data?.records || [];
|
total.value = res.total ?? res.data?.total ?? 0;
|
})
|
.catch(() => {
|
tableLoading.value = false;
|
});
|
};
|
|
const handleSelectionChange = (selection) => {
|
selectedRows.value = selection;
|
};
|
|
const resetFormData = () => {
|
Object.keys(form).forEach((key) => {
|
form[key] = "";
|
});
|
form.area = undefined;
|
form.deposit = undefined;
|
form.rent = undefined;
|
costList.value = [];
|
};
|
|
const openForm = (type, row) => {
|
operationType.value = type;
|
resetFormData();
|
if (type === "add") {
|
dialogVisible.value = true;
|
} else {
|
currentId.value = row.id;
|
getHouseLeaseDetail({ id: row.id }).then((res) => {
|
const detail = res.data || res;
|
Object.keys(form).forEach((key) => {
|
if (detail[key] !== undefined && detail[key] !== null) {
|
form[key] = detail[key];
|
}
|
});
|
costList.value = detail.costList || detail.costVOs || [];
|
dialogVisible.value = true;
|
});
|
}
|
};
|
|
const submitForm = () => {
|
proxy.$refs["formRef"].validate((valid) => {
|
if (!valid) return;
|
form.costList = costList.value;
|
addOrUpdateHouseLease(form).then(() => {
|
proxy.$modal.msgSuccess("保存成功");
|
closeDialog();
|
getList();
|
});
|
});
|
};
|
|
const closeDialog = () => {
|
proxy.resetForm("formRef");
|
dialogVisible.value = false;
|
};
|
|
const handleDelete = () => {
|
if (selectedRows.value.length === 0) {
|
proxy.$modal.msgWarning("请选择数据");
|
return;
|
}
|
const ids = selectedRows.value.map((item) => item.id);
|
ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "提示", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
delHouseLease(ids).then(() => {
|
proxy.$modal.msgSuccess("删除成功");
|
getList();
|
});
|
})
|
.catch(() => {});
|
};
|
|
const handleRowDelete = (row) => {
|
ElMessageBox.confirm("该租赁记录将被删除,是否确认删除?", "提示", {
|
confirmButtonText: "确认",
|
cancelButtonText: "取消",
|
type: "warning",
|
})
|
.then(() => {
|
delHouseLease([row.id]).then(() => {
|
proxy.$modal.msgSuccess("删除成功");
|
getList();
|
});
|
})
|
.catch(() => {});
|
};
|
|
const handleExport = () => {
|
proxy.download("/houseLease/export", { ...searchForm }, "房屋租赁管理.xlsx");
|
};
|
|
onMounted(() => {
|
getList();
|
});
|
</script>
|
|
<style scoped lang="scss">
|
.search_form {
|
margin-bottom: 10px;
|
}
|
|
.actions {
|
display: flex;
|
justify-content: space-between;
|
margin-bottom: 10px;
|
}
|
|
.warn-date {
|
color: #e6a23c;
|
font-weight: 600;
|
}
|
|
.overdue-date {
|
color: #f56c6c;
|
font-weight: 600;
|
}
|
|
.cost-summary {
|
margin-top: 12px;
|
display: flex;
|
flex-wrap: wrap;
|
gap: 16px;
|
justify-content: flex-end;
|
font-size: 13px;
|
color: #606266;
|
|
.cost-total {
|
font-weight: 600;
|
color: #f56c6c;
|
}
|
}
|
</style>
|