Files
estate/src/views/system/houseRental/index.vue
2026-09-02 17:32:15 +08:00

555 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="h-full p-2 flex flex-col">
<pageHeader></pageHeader>
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card>
<div class="h-full flex flex-align-center">
<el-form ref="queryFormRef" :inline="true" :model="queryParams" label-width="80px">
<el-form-item label="房屋类型" prop="houseUse">
<el-select v-model="queryParams.houseUse" placeholder="选择房屋类型" style="width: 240px">
<el-option v-for="item in houseUseTypeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="发布人" prop="rentalName">
<el-input v-model="queryParams.rentalName" clearable maxlength="15" placeholder="请输入" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</el-card>
</div>
</transition>
<el-card class="flex-1" shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<right-toolbar
:disable-delete="multiple"
:disable-edit="single"
:show-add="checkPermi(['houseRental:houseRental:add'])"
:show-delete="checkPermi(['houseRental:houseRental:remove'])"
:show-edit="checkPermi(['houseRental:houseRental:edit'])"
@update:add="handleAdd"
@update:edit="handleUpdate()"
@update:delete="handleDelete()"
></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="houseRentalList" border class="h-full" @selection-change="handleSelectionChange">
<el-table-column align="center" type="selection" width="55" />
<el-table-column align="center" label="房屋信息" prop="name" />
<el-table-column align="center" label="租赁状态" prop="rentalStatus">
<template #default="{ row }">
<DictTag :options="com_rental_status" :value="row.rentalStatus"></DictTag>
</template>
</el-table-column>
<el-table-column align="center" label="发布人" prop="rentalName" />
<el-table-column align="center" label="联系方式" prop="phone" />
<el-table-column align="center" label="发布时间" prop="pubTime" />
<el-table-column align="center" label="房屋类型" prop="houseUse">
<template #default="{ row }">
<span>{{ handleHouseUse(row.houseUse) }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="租赁方式" prop="rentalType">
<template #default="{ row }">
<DictTag :options="com_rental_method" :value="row.rentalType"></DictTag>
</template>
</el-table-column>
<el-table-column align="center" label="状态" prop="status">
<template #default="{ row }">
<span>{{ row.status === '0' ? '下架' : '上架' }}</span>
</template>
</el-table-column>
<el-table-column align="center" class-name="small-padding fixed-width" fixed="right" label="操作">
<template #default="scope">
<el-button v-if="scope.row.status === '0'" link type="primary" @click="sign(scope.row)">签约</el-button>
<el-button v-if="scope.row.status === '0'" link type="primary" @click="putOnSale(scope.row)">上架</el-button>
<el-button v-else link type="primary" @click="putOnSale(scope.row)">下架</el-button>
<el-button link type="primary" @click="handleMain(scope.row)">详情</el-button>
<el-button v-has-permi="['houseRental:houseRental:query']" link type="primary" @click="handleUpdate(scope.row)">编辑</el-button>
<el-button v-has-permi="['houseRental:houseRental:remove']" link type="primary" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNum" :total="total" @pagination="getList" />
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" append-to-body width="680px">
<el-form ref="houseRentalFormRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="区域" prop="villageId">
<el-input v-model.trim="form.villageName" disabled maxlength="20" placeholder="请输入区域" />
</el-form-item>
<el-form-item label="承租方" prop="tenantName">
<el-input v-model.trim="form.tenantName" maxlength="20" placeholder="请输入承租方" show-word-limit />
</el-form-item>
<el-form-item label="电话" prop="tenantPhone">
<el-input v-model.trim="form.tenantPhone" maxlength="11" placeholder="请输入电话" />
</el-form-item>
<el-form-item label="房号" prop="houseId">
<el-input v-model.trim="form.houseName" disabled maxlength="20" placeholder="请输入房号" show-word-limit />
</el-form-item>
<el-form-item label="面积" prop="houseArea">
<el-input v-model.trim="form.houseArea" disabled maxlength="20" placeholder="请输入面积" show-word-limit />
</el-form-item>
<el-form-item label="收费模式" prop="chargeMode">
<el-select v-model.trim="form.chargeMode" placeholder="请选择收费模式">
<el-option v-for="(item, index) in com_house_rental_pricing_model" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="收费标准" prop="chargeStandard">
<el-select v-model.trim="form.chargeStandard" placeholder="请选择收费标准">
<el-option v-for="(item, index) in com_house_rental_pricing" :key="index" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="合同编号" prop="contractNo">
<el-input v-model.trim="form.contractNo" maxlength="20" placeholder="请输入合同编号" show-word-limit />
</el-form-item>
<el-form-item label="合同状态" prop="contractStatus">
<el-input v-model.trim="form.contractStatus" maxlength="40" placeholder="请输入合同状态" show-word-limit />
</el-form-item>
<el-form-item label="租赁起始日" prop="leaseStartDate">
<el-date-picker
v-model.trim="form.leaseStartDate"
:disabled-date="disabledStartDate"
format="YYYY-MM-DD"
placeholder="请选择租赁起始日"
style="width: 100%"
type="date"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item label="租赁终止日" prop="leaseEndDate">
<el-date-picker
v-model.trim="form.leaseEndDate"
:disabled-date="disabledEndDate"
format="YYYY-MM-DD"
placeholder="请选择租赁终止日"
style="width: 100%"
type="date"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item label="租金" prop="rentAmount">
<el-input v-model.trim.number="form.rentAmount" maxlength="8" placeholder="请输入租金">
<template #append>
<span>元/月</span>
</template>
</el-input>
</el-form-item>
<el-form-item label="押金" prop="depositAmount">
<el-input v-model.trim.number="form.depositAmount" maxlength="8" placeholder="请输入押金">
<template #append>
<span>元</span>
</template>
</el-input>
</el-form-item>
<el-form-item label="合同附件">
<el-upload
ref="uploadRef"
v-model:file-list="showImgList"
:action="uploadApi"
:auto-upload="false"
:limit="MAX_UPLOAD_FILES"
:multiple="true"
:on-preview="handlePreview"
:onChange="handleChange"
:onExceed="handleExceed"
:show-file-list="true"
accept=".png,.jpg,.jpeg, .pdf"
class="upload-demo"
drag
name="files"
style="width: 100%"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text"><em>点击</em>或者将文件<em>拖拽</em>到这里上传</div>
<template #tip>
<div class="el-upload__tip">支持 .png, .jpg, .pdf</div>
</template>
</el-upload>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" name="HouseRental" setup>
import pageHeader from '@/components/Pageheader/index.vue';
import { changeHouseRedientStatus, delHouseRental, HouseRedientSign, HouseRedientSignUpload, listHouseRental } from '@/api/system/houseRental';
import { HouseRentalQuery, HouseRentalSigningVO, HouseRentalVO } from '@/api/system/houseRental/type';
import { checkPermi } from '@/utils/permission';
import { listHouseUseType_add_house } from '@/api/system/houseUse';
import { validator } from '@/utils/Reg';
import { UploadFilled } from '@element-plus/icons-vue';
import { ElMessage, UploadUserFile } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { com_rental_status, com_rental_method, com_house_rental_pricing_model, com_house_rental_pricing } = toRefs<any>(
proxy?.useDict('com_rental_status', 'com_rental_method', 'com_house_rental_pricing_model', 'com_house_rental_pricing')
);
const uploadApi = '/houseRental/uploadImages';
const router = useRouter();
const houseRentalList = ref<HouseRentalVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const houseRentalFormRef = ref<ElFormInstance>();
const villageInfo = localStorage.getItem('villageInfo');
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
type HouseRentalFormType = HouseRentalSigningVO & {
villageName?: string;
houseName?: string;
houseArea?: number;
contractUrl?: string;
};
const initFormData: HouseRentalFormType = {
contractName: '',
rentAmount: undefined,
depositAmount: undefined,
'createTime': undefined,
'id': undefined,
'rentalId': undefined,
'contractNo': undefined,
'tenantName': undefined,
'tenantPhone': undefined,
'chargeMode': undefined,
'chargeStandard': undefined,
'contractStatus': undefined,
'leaseStartDate': undefined,
'leaseEndDate': undefined,
'houseId': undefined,
'villageId': undefined,
'contractUrl': undefined,
'remark': undefined
};
const data = reactive<PageData<HouseRentalFormType, HouseRentalQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
houseUse: '',
rentalName: undefined,
rent: undefined,
rentalType: undefined,
houseFace: undefined,
houseArea: undefined,
floorNo: undefined,
inTime: undefined,
facilities: undefined,
houseImageUrl: undefined,
supInfo: undefined,
phone: undefined,
vx: undefined,
email: undefined,
houseId: undefined
},
rules: {
contractNo: [
{ required: true, message: '请输入合同编号', trigger: 'blur' },
{ min: 1, max: 20, message: '合同编号长度必须在 1 到 20 个字符之间', trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9_\-]+$/, message: '合同编号只能包含字母、数字,下划线和短横线', trigger: 'blur' }
],
tenantName: [
{ required: true, message: '请输入承租方姓名', trigger: 'blur' },
{ min: 1, max: 20, message: '承租方姓名长度必须在 1 到 20 个字符之间', trigger: 'blur' }
],
tenantPhone: [
{ required: true, message: '请输入承租方联系电话', trigger: 'blur' },
{
validator: (_: any, value: string, callback: any) => {
if (!validator(value, 'phone')) {
callback(new Error('承租方联系电话格式错误'));
} else {
callback();
}
},
trigger: ['blur', 'change']
}
],
chargeMode: [{ required: true, message: '请选择收费模式', trigger: 'change' }],
chargeStandard: [{ required: true, message: '请输入收费标准', trigger: 'blur' }],
contractStatus: [{ required: true, message: '请选择合同状态', trigger: 'change' }],
leaseStartDate: [{ required: true, message: '请选择租赁起始日', trigger: 'change' }],
leaseEndDate: [{ required: true, message: '请选择租赁终止日', trigger: 'change' }],
houseId: [{ required: true, message: '请选择房屋', trigger: 'change' }],
villageId: [{ required: true, message: '请选择小区', trigger: 'change' }],
houseArea: [{ required: true, message: '请输入房屋面积', trigger: 'blur' }],
rentAmount: [{ required: true, message: '请输入租金', trigger: 'blur' }],
depositAmount: [{ required: true, message: '请输入押金', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 获取今日零点时间戳 */
const todayStart = new Date();
todayStart.setHours(0, 0, 0, 0);
const TODAY = todayStart.getTime();
/**
* 起始日禁用规则:
* 1. 今日之前不可选
* 2. 若已选结束日,则结束日之后不可选
*/
function disabledStartDate(date: Date): boolean {
if (date.getTime() < TODAY) return true;
if (!form.value.leaseEndDate) return false;
return date.getTime() > new Date(form.value.leaseEndDate).getTime();
}
/**
* 结束日禁用规则:
* 1. 今日之前不可选
* 2. 若已选起始日,则起始日之前不可选
*/
function disabledEndDate(date: Date): boolean {
if (date.getTime() < TODAY) return true;
if (!form.value.leaseStartDate) return false;
return date.getTime() < new Date(form.value.leaseStartDate).getTime();
}
/** 查询房屋租赁管理列表 */
const getList = async () => {
loading.value = true;
listHouseRental(queryParams.value)
.then((res) => {
houseRentalList.value = res.rows;
total.value = res.total;
})
.finally(() => {
loading.value = false;
});
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
houseRentalFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.houseUse = '';
queryParams.value.rentalName = '';
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: HouseRentalVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
router.push({
path: '/house/addhouseRental'
});
};
function handlePreview(uploadFile: UploadFile) {
window.open(uploadFile.url);
}
/** 修改按钮操作 */
const handleUpdate = (row?: HouseRentalVO) => {
const id = row?.id || ids.value[0];
router.push({
path: '/house/edithouseRental',
query: {
id: id
}
});
};
/** 详情按钮操作 */
const handleMain = (row?: HouseRentalVO) => {
router.push({
path: '/house/houseMain',
query: {
id: row.id
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: HouseRentalVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('该信息删除后无法恢复,确定要删除吗?').finally(() => (loading.value = false));
await delHouseRental(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
const houseUseTypeList = ref([]);
function getHouseUseType() {
listHouseUseType_add_house().then((res) => {
houseUseTypeList.value = res.data;
});
}
function handleHouseUse(houseUser: string) {
const item = houseUseTypeList.value.find((item) => item.id === houseUser);
return item?.name || '';
}
// 签约
function sign(row: HouseRentalVO) {
clearForm();
form.value = {
...initFormData,
...row.signingList?.[0]
};
const info = JSON.parse(villageInfo || '{}');
form.value.villageName = info.villageName;
form.value.houseName = row.name;
form.value.houseArea = row.houseArea;
form.value.tenantPhone = row.signingList.length > 0 ? row.signingList[0].tenantPhone : '';
form.value.houseId = row.houseId;
form.value.villageId = info.villageId;
form.value.rentalId = row.id;
if (row.signingList && row.signingList.length > 0) {
const names = row.signingList[0].contractName ? row.signingList[0].contractName.split(',') : [];
const list = row.signingList[0].contractUrl ? row.signingList[0].contractUrl.split(',') : [];
if (list.length > 0) {
showImgList.value = list.map((item, index) => {
return {
uid: index,
name: names[index] || (item ? item.split('.com/')[1].split('/')[3] : ''),
url: item,
isLink: true
};
});
} else {
showImgList.value = [];
}
} else {
showImgList.value = [];
}
dialog.visible = true;
dialog.title = '签约';
}
// 上架
function putOnSale(row: HouseRentalVO) {
changeHouseRedientStatus(row.id).then(() => {
proxy?.$modal.msgSuccess('操作成功');
getList();
});
}
const uploadRef = ref();
const MAX_UPLOAD_FILES = 9;
const showImgList = ref([]);
// 超出总数量拦截,不再只限制单次选择
const handleExceed = () => {
ElMessage.warning(`最多只能上传 ${MAX_UPLOAD_FILES} 个附件`);
};
const handleChange = (file: UploadUserFile) => {
// 过滤上传文件-只允许图片,pdf格式
if (!/^\.(jpg|jpeg|png|gif|pdf)$/i.test(file.name)) {
ElMessage.warning('只允许上传图片,pdf格式');
uploadRef.value?.handleRemove(file);
return;
}
// 实时判断当前已存在图片总数
if (showImgList.value.length > MAX_UPLOAD_FILES) {
ElMessage.warning(`最多上传${MAX_UPLOAD_FILES}个附件,无法继续添加`);
// 清除刚选中的文件,防止缓存残留
uploadRef.value?.handleRemove(file);
return;
}
};
// 批量上传校验图片数量,前后端数量匹配校验
const uploadAllCarousel = async () => {
const filterArr = showImgList.value.filter((item) => {
return !item.isLink && item.raw;
});
const oldArr = showImgList.value.filter((item) => item.isLink && !item.raw);
if (filterArr.length > 0) {
const formData = new FormData();
filterArr.forEach((fileItem) => {
formData.append('files', fileItem.raw!);
});
const res = await HouseRedientSignUpload(uploadApi, formData);
// 校验后端返回图片数量和本地选中数量一致
if (res.code !== 200 || !res.data?.success?.length) {
ElMessage.warning(res.msg || '文件上传失败');
throw new Error('上传失败');
}
return [...res.data.success.map((item: any) => item.url), ...oldArr.map((item) => item.url)];
} else {
return [...oldArr.map((item) => item.url)];
}
};
function submitForm() {
houseRentalFormRef.value.validate(async (valid: boolean) => {
if (valid) {
const arr = await uploadAllCarousel();
// 拼接最终图片字符串
form.value.contractUrl = arr.join(',');
form.value.rentAmount = String(form.value.rentAmount) || 0;
form.value.depositAmount = String(form.value.depositAmount) || 0;
HouseRedientSign(form.value).then(() => {
proxy?.$modal.msgSuccess('操作成功');
getList();
dialog.visible = false;
});
}
});
}
function clearForm() {
form.value = { ...initFormData };
}
onMounted(() => {
getList();
getHouseUseType();
});
</script>
<style lang="scss" scoped>
.el-table {
height: calc(100% - 100px);
}
</style>