|
|
@@ -0,0 +1,956 @@
|
|
|
+<template>
|
|
|
+ <el-container class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
|
|
+ <el-main class="h-full p-0">
|
|
|
+ <!-- 页面标题区域 - 缩小占比 -->
|
|
|
+ <div class="bg-gradient-to-r from-blue-600 to-purple-700 text-white py-8 px-8">
|
|
|
+ <div class="w-full">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div>
|
|
|
+ <h1 class="text-3xl font-extrabold mb-2">{{ propertyInfo.buildingNumber }}</h1>
|
|
|
+ <p class="text-base opacity-90">{{ propertyInfo.address }}</p>
|
|
|
+ </div>
|
|
|
+ <div class="text-right flex items-center gap-6">
|
|
|
+ <!-- AR看房按钮 - 重新设计 -->
|
|
|
+ <div class="relative">
|
|
|
+ <button
|
|
|
+ @click="openARViewing"
|
|
|
+ class="ar-viewing-btn group relative overflow-hidden bg-gradient-to-r from-pink-500 via-purple-500 to-indigo-500 text-white px-6 py-3 rounded-2xl font-semibold text-sm shadow-2xl transform transition-all duration-300 hover:scale-105 hover:shadow-pink-500/25 active:scale-95"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="absolute inset-0 bg-gradient-to-r from-pink-600 via-purple-600 to-indigo-600 opacity-0 group-hover:opacity-100 transition-opacity duration-300"
|
|
|
+ ></div>
|
|
|
+ <div class="relative flex items-center gap-2">
|
|
|
+ <div class="w-5 h-5 relative">
|
|
|
+ <div
|
|
|
+ class="absolute inset-0 bg-white rounded-full opacity-20 animate-ping"
|
|
|
+ ></div>
|
|
|
+ <el-icon class="relative z-10"><Camera /></el-icon>
|
|
|
+ </div>
|
|
|
+ <span class="relative z-10">AR看房</span>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent -skew-x-12 -translate-x-full group-hover:translate-x-full transition-transform duration-1000"
|
|
|
+ ></div>
|
|
|
+ </button>
|
|
|
+ <div
|
|
|
+ class="absolute -inset-1 bg-gradient-to-r from-pink-500 via-purple-500 to-indigo-500 rounded-2xl blur opacity-30 group-hover:opacity-50 transition-opacity duration-300 -z-10"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div>
|
|
|
+ <el-tag
|
|
|
+ :type="propertyInfo.status === '可租' ? 'success' : 'warning'"
|
|
|
+ size="large"
|
|
|
+ class="mb-2"
|
|
|
+ >
|
|
|
+ {{ propertyInfo.status }}
|
|
|
+ </el-tag>
|
|
|
+ <div class="text-xl font-bold">{{ propertyInfo.priceRange }} 元/月</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
+ <div class="py-8 px-8">
|
|
|
+ <div class="w-full">
|
|
|
+ <!-- 租户信息 - 仅已出租显示 -->
|
|
|
+ <el-card
|
|
|
+ v-if="propertyInfo.status === '已租'"
|
|
|
+ class="mb-6 shadow-lg rounded-2xl border-0"
|
|
|
+ >
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center">
|
|
|
+ <el-icon class="mr-2 text-blue-500"><User /></el-icon>
|
|
|
+ <span class="text-lg font-semibold">租户信息</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-row :gutter="24">
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">租户姓名</label>
|
|
|
+ <div class="text-lg font-semibold text-gray-800">{{ tenantInfo.name }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">联系电话</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ tenantInfo.phone }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">身份证号</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ tenantInfo.idCard }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">入住时间</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ tenantInfo.moveInDate }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">租期</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ tenantInfo.leaseTerm }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">月租金</label>
|
|
|
+ <div class="text-lg font-semibold text-blue-600">
|
|
|
+ {{ tenantInfo.monthlyRent }} 元
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 房屋基本信息 -->
|
|
|
+ <el-card class="mb-6 shadow-lg rounded-2xl border-0">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center">
|
|
|
+ <el-icon class="mr-2 text-green-500"><Home /></el-icon>
|
|
|
+ <span class="text-lg font-semibold">房屋基本信息</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-row :gutter="24">
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">建筑面积</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ propertyInfo.area }} ㎡</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8" v-if="propertyInfo.floor">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">所在楼层</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ propertyInfo.floor }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">装修情况</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ propertyInfo.facilities }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">建成年份</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ basicInfo.buildYear }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">房屋朝向</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ basicInfo.orientation }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">房屋类型</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ basicInfo.houseType }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 房屋设备资产 - 简化版 -->
|
|
|
+ <el-card class="mb-6 shadow-lg rounded-2xl border-0">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div class="flex items-center">
|
|
|
+ <el-icon class="mr-2 text-purple-500"><Cpu /></el-icon>
|
|
|
+ <span class="text-lg font-semibold">房屋设备资产</span>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-4">
|
|
|
+ <!-- 设备筛选 -->
|
|
|
+ <el-select
|
|
|
+ v-model="assetFilter.category"
|
|
|
+ placeholder="设备类型"
|
|
|
+ size="small"
|
|
|
+ style="width: 120px"
|
|
|
+ @change="filterAssets"
|
|
|
+ >
|
|
|
+ <el-option label="全部" value=""></el-option>
|
|
|
+ <el-option label="厨房电器" value="厨房电器"></el-option>
|
|
|
+ <el-option label="生活电器" value="生活电器"></el-option>
|
|
|
+ <el-option label="清洁电器" value="清洁电器"></el-option>
|
|
|
+ <el-option label="家具" value="家具"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-row :gutter="16">
|
|
|
+ <el-col
|
|
|
+ :xs="24"
|
|
|
+ :sm="12"
|
|
|
+ :md="8"
|
|
|
+ :lg="6"
|
|
|
+ v-for="asset in filteredAssets"
|
|
|
+ :key="asset.id"
|
|
|
+ class="mb-4"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow bg-white"
|
|
|
+ >
|
|
|
+ <div class="mb-3">
|
|
|
+ <h4 class="font-semibold text-gray-800 text-lg">{{ asset.name }}</h4>
|
|
|
+ <p class="text-sm text-gray-500">{{ asset.category }}</p>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-600 space-y-1">
|
|
|
+ <p><strong>品牌:</strong>{{ asset.brand }}</p>
|
|
|
+ <p><strong>型号:</strong>{{ asset.model }}</p>
|
|
|
+ <p>
|
|
|
+ <strong>价值:</strong
|
|
|
+ ><span class="text-green-600 font-semibold">{{ asset.value }} 元</span>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <div v-if="filteredAssets.length === 0" class="text-center py-8 text-gray-500">
|
|
|
+ 暂无符合条件的设备资产
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 资产维护记录 -->
|
|
|
+ <el-card class="mb-6 shadow-lg rounded-2xl border-0">
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div class="flex items-center">
|
|
|
+ <el-icon class="mr-2 text-orange-500"><Wrench /></el-icon>
|
|
|
+ <span class="text-lg font-semibold">资产维护记录</span>
|
|
|
+ </div>
|
|
|
+ <el-button type="primary" size="small" @click="showAddMaintenanceDialog = true">
|
|
|
+ <el-icon class="mr-1"><Plus /></el-icon>
|
|
|
+ 添加记录
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-table :data="maintenanceRecords" stripe style="width: 100%">
|
|
|
+ <el-table-column prop="date" label="维护日期" width="120" />
|
|
|
+ <el-table-column prop="type" label="维护类型" width="120" />
|
|
|
+ <el-table-column prop="description" label="维护内容" />
|
|
|
+ <el-table-column prop="cost" label="费用(元)" width="100" />
|
|
|
+ <el-table-column prop="status" label="状态" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag
|
|
|
+ :type="scope.row.status === '已完成' ? 'success' : 'warning'"
|
|
|
+ size="small"
|
|
|
+ >
|
|
|
+ {{ scope.row.status }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button type="primary" size="small" @click="viewMaintenanceDetail(scope.row)">
|
|
|
+ 查看
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 合同信息 - 仅已出租显示 -->
|
|
|
+ <el-card
|
|
|
+ v-if="propertyInfo.status === '已租'"
|
|
|
+ class="mb-6 shadow-lg rounded-2xl border-0"
|
|
|
+ >
|
|
|
+ <template #header>
|
|
|
+ <div class="flex items-center">
|
|
|
+ <el-icon class="mr-2 text-purple-500"><FileText /></el-icon>
|
|
|
+ <span class="text-lg font-semibold">合同信息</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-row :gutter="24">
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">合同编号</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ contractInfo.contractNumber }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">签约日期</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ contractInfo.signDate }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">合同期限</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ contractInfo.duration }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">到期日期</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ contractInfo.endDate }}</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">押金</label>
|
|
|
+ <div class="text-lg text-gray-800">{{ contractInfo.deposit }} 元</div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="12" :md="8">
|
|
|
+ <div class="mb-4">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">合同状态</label>
|
|
|
+ <el-tag type="success">{{ contractInfo.status }}</el-tag>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 操作按钮区域 -->
|
|
|
+ <div class="flex justify-center gap-4">
|
|
|
+ <el-button
|
|
|
+ v-if="propertyInfo.status === '可租'"
|
|
|
+ type="primary"
|
|
|
+ size="large"
|
|
|
+ @click="showRentalDialog = true"
|
|
|
+ class="px-8 py-3"
|
|
|
+ >
|
|
|
+ <el-icon class="mr-2"><Plus /></el-icon>
|
|
|
+ 出租房屋
|
|
|
+ </el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="propertyInfo.status === '已租'"
|
|
|
+ type="danger"
|
|
|
+ size="large"
|
|
|
+ @click="showTerminateDialog = true"
|
|
|
+ class="px-8 py-3"
|
|
|
+ >
|
|
|
+ <el-icon class="mr-2"><X /></el-icon>
|
|
|
+ 退租房屋
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-main>
|
|
|
+
|
|
|
+ <!-- 出租流程表单对话框 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="showRentalDialog"
|
|
|
+ title="房屋出租"
|
|
|
+ width="800px"
|
|
|
+ :before-close="handleCloseRentalDialog"
|
|
|
+ >
|
|
|
+ <el-form :model="rentalForm" :rules="rentalRules" ref="rentalFormRef" label-width="120px">
|
|
|
+ <el-steps :active="currentStep" finish-status="success" class="mb-8">
|
|
|
+ <el-step title="租户信息" />
|
|
|
+ <el-step title="合同条款" />
|
|
|
+ <el-step title="确认提交" />
|
|
|
+ </el-steps>
|
|
|
+
|
|
|
+ <!-- 第一步:租户信息 -->
|
|
|
+ <div v-show="currentStep === 0">
|
|
|
+ <el-form-item label="租户姓名" prop="tenantName">
|
|
|
+ <el-input v-model="rentalForm.tenantName" placeholder="请输入租户姓名" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="联系电话" prop="tenantPhone">
|
|
|
+ <el-input v-model="rentalForm.tenantPhone" placeholder="请输入联系电话" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="身份证号" prop="tenantIdCard">
|
|
|
+ <el-input v-model="rentalForm.tenantIdCard" placeholder="请输入身份证号" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="紧急联系人" prop="emergencyContact">
|
|
|
+ <el-input v-model="rentalForm.emergencyContact" placeholder="请输入紧急联系人" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="紧急联系电话" prop="emergencyPhone">
|
|
|
+ <el-input v-model="rentalForm.emergencyPhone" placeholder="请输入紧急联系电话" />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 第二步:合同条款 -->
|
|
|
+ <div v-show="currentStep === 1">
|
|
|
+ <el-form-item label="月租金" prop="monthlyRent">
|
|
|
+ <el-input-number v-model="rentalForm.monthlyRent" :min="0" :step="100" />
|
|
|
+ <span class="ml-2 text-gray-500">元</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="押金" prop="deposit">
|
|
|
+ <el-input-number v-model="rentalForm.deposit" :min="0" :step="100" />
|
|
|
+ <span class="ml-2 text-gray-500">元</span>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="租期" prop="leaseTerm">
|
|
|
+ <el-select v-model="rentalForm.leaseTerm" placeholder="请选择租期">
|
|
|
+ <el-option label="6个月" value="6个月" />
|
|
|
+ <el-option label="1年" value="1年" />
|
|
|
+ <el-option label="2年" value="2年" />
|
|
|
+ <el-option label="3年" value="3年" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="入住日期" prop="moveInDate">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="rentalForm.moveInDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="请选择入住日期"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="特殊条款" prop="specialTerms">
|
|
|
+ <el-input
|
|
|
+ v-model="rentalForm.specialTerms"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入特殊条款(可选)"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 第三步:确认信息 -->
|
|
|
+ <div v-show="currentStep === 2">
|
|
|
+ <div class="bg-gray-50 p-6 rounded-lg">
|
|
|
+ <h3 class="text-lg font-semibold mb-4">请确认以下信息:</h3>
|
|
|
+ <el-row :gutter="24">
|
|
|
+ <el-col :span="12">
|
|
|
+ <p><strong>租户姓名:</strong>{{ rentalForm.tenantName }}</p>
|
|
|
+ <p><strong>联系电话:</strong>{{ rentalForm.tenantPhone }}</p>
|
|
|
+ <p><strong>身份证号:</strong>{{ rentalForm.tenantIdCard }}</p>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <p><strong>月租金:</strong>{{ rentalForm.monthlyRent }} 元</p>
|
|
|
+ <p><strong>押金:</strong>{{ rentalForm.deposit }} 元</p>
|
|
|
+ <p><strong>租期:</strong>{{ rentalForm.leaseTerm }}</p>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="flex justify-between">
|
|
|
+ <el-button v-if="currentStep > 0" @click="currentStep--">上一步</el-button>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <el-button @click="handleCloseRentalDialog">取消</el-button>
|
|
|
+ <el-button v-if="currentStep < 2" type="primary" @click="nextStep">下一步</el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="currentStep === 2"
|
|
|
+ type="primary"
|
|
|
+ @click="submitRental"
|
|
|
+ :loading="submitting"
|
|
|
+ >确认出租</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 退租确认对话框 -->
|
|
|
+ <el-dialog v-model="showTerminateDialog" title="退租确认" width="600px">
|
|
|
+ <div class="mb-6">
|
|
|
+ <h3 class="text-lg font-semibold mb-4">合同信息</h3>
|
|
|
+ <div class="bg-gray-50 p-4 rounded-lg">
|
|
|
+ <p><strong>合同编号:</strong>{{ contractInfo.contractNumber }}</p>
|
|
|
+ <p><strong>租户姓名:</strong>{{ tenantInfo.name }}</p>
|
|
|
+ <p><strong>租期:</strong>{{ contractInfo.duration }}</p>
|
|
|
+ <p><strong>到期日期:</strong>{{ contractInfo.endDate }}</p>
|
|
|
+ <p><strong>押金:</strong>{{ contractInfo.deposit }} 元</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-form :model="terminateForm" label-width="120px">
|
|
|
+ <el-form-item label="退租日期">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="terminateForm.terminateDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="请选择退租日期"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="退租原因">
|
|
|
+ <el-input
|
|
|
+ v-model="terminateForm.reason"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入退租原因"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="押金处理">
|
|
|
+ <el-radio-group v-model="terminateForm.depositHandling">
|
|
|
+ <el-radio label="全额退还">全额退还</el-radio>
|
|
|
+ <el-radio label="部分扣除">部分扣除</el-radio>
|
|
|
+ <el-radio label="全额扣除">全额扣除</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item v-if="terminateForm.depositHandling === '部分扣除'" label="扣除金额">
|
|
|
+ <el-input-number
|
|
|
+ v-model="terminateForm.deductAmount"
|
|
|
+ :min="0"
|
|
|
+ :max="contractInfo.deposit"
|
|
|
+ />
|
|
|
+ <span class="ml-2 text-gray-500">元</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <div class="flex justify-end gap-2">
|
|
|
+ <el-button @click="showTerminateDialog = false">取消</el-button>
|
|
|
+ <el-button type="danger" @click="confirmTerminate" :loading="terminating"
|
|
|
+ >确认退租</el-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 添加维护记录对话框 -->
|
|
|
+ <el-dialog v-model="showAddMaintenanceDialog" title="添加维护记录" width="500px">
|
|
|
+ <el-form :model="maintenanceForm" label-width="100px">
|
|
|
+ <el-form-item label="维护日期">
|
|
|
+ <el-date-picker v-model="maintenanceForm.date" type="date" placeholder="请选择维护日期" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="维护类型">
|
|
|
+ <el-select v-model="maintenanceForm.type" placeholder="请选择维护类型">
|
|
|
+ <el-option label="日常保养" value="日常保养" />
|
|
|
+ <el-option label="设备维修" value="设备维修" />
|
|
|
+ <el-option label="装修改造" value="装修改造" />
|
|
|
+ <el-option label="安全检查" value="安全检查" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="维护内容">
|
|
|
+ <el-input
|
|
|
+ v-model="maintenanceForm.description"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入维护内容"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="费用">
|
|
|
+ <el-input-number v-model="maintenanceForm.cost" :min="0" :step="10" />
|
|
|
+ <span class="ml-2 text-gray-500">元</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <div class="flex justify-end gap-2">
|
|
|
+ <el-button @click="showAddMaintenanceDialog = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="addMaintenanceRecord">确认添加</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </el-container>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, onMounted, reactive, ref } from 'vue'
|
|
|
+import {
|
|
|
+ ElButton,
|
|
|
+ ElCard,
|
|
|
+ ElCol,
|
|
|
+ ElContainer,
|
|
|
+ ElDatePicker,
|
|
|
+ ElDialog,
|
|
|
+ ElForm,
|
|
|
+ ElFormItem,
|
|
|
+ ElIcon,
|
|
|
+ ElInput,
|
|
|
+ ElInputNumber,
|
|
|
+ ElMain,
|
|
|
+ ElMessage,
|
|
|
+ ElOption,
|
|
|
+ ElRadio,
|
|
|
+ ElRadioGroup,
|
|
|
+ ElRow,
|
|
|
+ ElSelect,
|
|
|
+ ElStep,
|
|
|
+ ElSteps,
|
|
|
+ ElTable,
|
|
|
+ ElTableColumn,
|
|
|
+ ElTag,
|
|
|
+} from 'element-plus'
|
|
|
+import { Camera, Cpu, FileText, Home, Plus, User, Wrench, X } from 'lucide-vue-next'
|
|
|
+
|
|
|
+// 响应式数据
|
|
|
+const showRentalDialog = ref(false)
|
|
|
+const showTerminateDialog = ref(false)
|
|
|
+const showAddMaintenanceDialog = ref(false)
|
|
|
+const currentStep = ref(0)
|
|
|
+const submitting = ref(false)
|
|
|
+const terminating = ref(false)
|
|
|
+
|
|
|
+// 房屋信息
|
|
|
+const propertyInfo = ref({
|
|
|
+ id: 1,
|
|
|
+ buildingNumber: 'A栋-101',
|
|
|
+ priceRange: '1200-1500',
|
|
|
+ address: '高新区科技大道123号',
|
|
|
+ area: 85,
|
|
|
+ status: '已租', // 可租 或 已租
|
|
|
+ floor: '1楼',
|
|
|
+ facilities: '家具齐全',
|
|
|
+})
|
|
|
+
|
|
|
+// 房屋基本信息
|
|
|
+const basicInfo = ref({
|
|
|
+ buildYear: '2020年',
|
|
|
+ orientation: '南北通透',
|
|
|
+ houseType: '两室一厅',
|
|
|
+})
|
|
|
+
|
|
|
+// 设备资产筛选
|
|
|
+const assetFilter = reactive({
|
|
|
+ category: '',
|
|
|
+})
|
|
|
+
|
|
|
+// 设备资产数据 - 简化版
|
|
|
+const assets = ref([
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ name: '海尔冰箱',
|
|
|
+ category: '厨房电器',
|
|
|
+ brand: '海尔',
|
|
|
+ model: 'BCD-215STPH',
|
|
|
+ value: 2800,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ name: '格力空调',
|
|
|
+ category: '生活电器',
|
|
|
+ brand: '格力',
|
|
|
+ model: 'KFR-35GW',
|
|
|
+ value: 3200,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ name: '小天鹅洗衣机',
|
|
|
+ category: '清洁电器',
|
|
|
+ brand: '小天鹅',
|
|
|
+ model: 'TB80V23H',
|
|
|
+ value: 1800,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ name: '美的微波炉',
|
|
|
+ category: '厨房电器',
|
|
|
+ brand: '美的',
|
|
|
+ model: 'M3-L233B',
|
|
|
+ value: 600,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 5,
|
|
|
+ name: '布艺沙发',
|
|
|
+ category: '家具',
|
|
|
+ brand: '宜家',
|
|
|
+ model: 'KIVIK',
|
|
|
+ value: 2500,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 6,
|
|
|
+ name: '电热水器',
|
|
|
+ category: '生活电器',
|
|
|
+ brand: '史密斯',
|
|
|
+ model: 'F560',
|
|
|
+ value: 1500,
|
|
|
+ },
|
|
|
+])
|
|
|
+
|
|
|
+// 筛选后的设备资产
|
|
|
+const filteredAssets = computed(() => {
|
|
|
+ return assets.value.filter((asset) => {
|
|
|
+ const categoryMatch = !assetFilter.category || asset.category === assetFilter.category
|
|
|
+ return categoryMatch
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+// 租户信息
|
|
|
+const tenantInfo = ref({
|
|
|
+ name: '张三',
|
|
|
+ phone: '13800138000',
|
|
|
+ idCard: '320123199001011234',
|
|
|
+ moveInDate: '2024-01-15',
|
|
|
+ leaseTerm: '1年',
|
|
|
+ monthlyRent: 1350,
|
|
|
+})
|
|
|
+
|
|
|
+// 合同信息
|
|
|
+const contractInfo = ref({
|
|
|
+ contractNumber: 'HT202401001',
|
|
|
+ signDate: '2024-01-10',
|
|
|
+ duration: '1年',
|
|
|
+ endDate: '2025-01-14',
|
|
|
+ deposit: 2700,
|
|
|
+ status: '执行中',
|
|
|
+})
|
|
|
+
|
|
|
+// 维护记录
|
|
|
+const maintenanceRecords = ref([
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ date: '2024-01-20',
|
|
|
+ type: '设备维修',
|
|
|
+ description: '洗衣机排水管堵塞维修',
|
|
|
+ cost: 200,
|
|
|
+ status: '已完成',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ date: '2024-02-15',
|
|
|
+ type: '设备保养',
|
|
|
+ description: '空调清洗保养',
|
|
|
+ cost: 150,
|
|
|
+ status: '已完成',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ date: '2024-03-10',
|
|
|
+ type: '设备更换',
|
|
|
+ description: '冰箱压缩机更换',
|
|
|
+ cost: 800,
|
|
|
+ status: '已完成',
|
|
|
+ },
|
|
|
+])
|
|
|
+
|
|
|
+// 出租表单
|
|
|
+const rentalForm = reactive({
|
|
|
+ tenantName: '',
|
|
|
+ tenantPhone: '',
|
|
|
+ tenantIdCard: '',
|
|
|
+ emergencyContact: '',
|
|
|
+ emergencyPhone: '',
|
|
|
+ monthlyRent: 1350,
|
|
|
+ deposit: 2700,
|
|
|
+ leaseTerm: '',
|
|
|
+ moveInDate: '',
|
|
|
+ specialTerms: '',
|
|
|
+})
|
|
|
+
|
|
|
+// 表单验证规则
|
|
|
+const rentalRules = {
|
|
|
+ tenantName: [{ required: true, message: '请输入租户姓名', trigger: 'blur' }],
|
|
|
+ tenantPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
|
|
|
+ tenantIdCard: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
|
|
|
+ monthlyRent: [{ required: true, message: '请输入月租金', trigger: 'blur' }],
|
|
|
+ deposit: [{ required: true, message: '请输入押金', trigger: 'blur' }],
|
|
|
+ leaseTerm: [{ required: true, message: '请选择租期', trigger: 'change' }],
|
|
|
+ moveInDate: [{ required: true, message: '请选择入住日期', trigger: 'change' }],
|
|
|
+}
|
|
|
+
|
|
|
+// 退租表单
|
|
|
+const terminateForm = reactive({
|
|
|
+ terminateDate: '',
|
|
|
+ reason: '',
|
|
|
+ depositHandling: '全额退还',
|
|
|
+ deductAmount: 0,
|
|
|
+})
|
|
|
+
|
|
|
+// 维护记录表单
|
|
|
+const maintenanceForm = reactive({
|
|
|
+ date: '',
|
|
|
+ type: '',
|
|
|
+ description: '',
|
|
|
+ cost: 0,
|
|
|
+})
|
|
|
+
|
|
|
+const rentalFormRef = ref()
|
|
|
+
|
|
|
+// 方法
|
|
|
+const openARViewing = () => {
|
|
|
+ ElMessage.info('正在启动AR看房功能...')
|
|
|
+ // 这里您可以调用您的AR看房接口
|
|
|
+}
|
|
|
+
|
|
|
+const filterAssets = () => {
|
|
|
+ // 筛选逻辑已通过计算属性实现
|
|
|
+}
|
|
|
+
|
|
|
+const nextStep = async () => {
|
|
|
+ if (currentStep.value === 0) {
|
|
|
+ const valid = await rentalFormRef.value.validateField([
|
|
|
+ 'tenantName',
|
|
|
+ 'tenantPhone',
|
|
|
+ 'tenantIdCard',
|
|
|
+ ])
|
|
|
+ if (valid) {
|
|
|
+ currentStep.value++
|
|
|
+ }
|
|
|
+ } else if (currentStep.value === 1) {
|
|
|
+ const valid = await rentalFormRef.value.validateField([
|
|
|
+ 'monthlyRent',
|
|
|
+ 'deposit',
|
|
|
+ 'leaseTerm',
|
|
|
+ 'moveInDate',
|
|
|
+ ])
|
|
|
+ if (valid) {
|
|
|
+ currentStep.value++
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleCloseRentalDialog = () => {
|
|
|
+ showRentalDialog.value = false
|
|
|
+ currentStep.value = 0
|
|
|
+ Object.keys(rentalForm).forEach((key) => {
|
|
|
+ if (typeof rentalForm[key] === 'string') {
|
|
|
+ rentalForm[key] = ''
|
|
|
+ } else if (typeof rentalForm[key] === 'number') {
|
|
|
+ rentalForm[key] = key === 'monthlyRent' ? 1350 : key === 'deposit' ? 2700 : 0
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const submitRental = async () => {
|
|
|
+ submitting.value = true
|
|
|
+ try {
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
|
+
|
|
|
+ propertyInfo.value.status = '已租'
|
|
|
+
|
|
|
+ tenantInfo.value = {
|
|
|
+ name: rentalForm.tenantName,
|
|
|
+ phone: rentalForm.tenantPhone,
|
|
|
+ idCard: rentalForm.tenantIdCard,
|
|
|
+ moveInDate: rentalForm.moveInDate,
|
|
|
+ leaseTerm: rentalForm.leaseTerm,
|
|
|
+ monthlyRent: rentalForm.monthlyRent,
|
|
|
+ }
|
|
|
+
|
|
|
+ contractInfo.value = {
|
|
|
+ contractNumber: 'HT' + Date.now(),
|
|
|
+ signDate: new Date().toISOString().split('T')[0],
|
|
|
+ duration: rentalForm.leaseTerm,
|
|
|
+ endDate: calculateEndDate(rentalForm.moveInDate, rentalForm.leaseTerm),
|
|
|
+ deposit: rentalForm.deposit,
|
|
|
+ status: '执行中',
|
|
|
+ }
|
|
|
+
|
|
|
+ ElMessage.success('房屋出租成功!')
|
|
|
+ handleCloseRentalDialog()
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('出租失败,请重试')
|
|
|
+ } finally {
|
|
|
+ submitting.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const confirmTerminate = async () => {
|
|
|
+ terminating.value = true
|
|
|
+ try {
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 1500))
|
|
|
+
|
|
|
+ propertyInfo.value.status = '可租'
|
|
|
+
|
|
|
+ ElMessage.success('退租成功!')
|
|
|
+ showTerminateDialog.value = false
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error('退租失败,请重试')
|
|
|
+ } finally {
|
|
|
+ terminating.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const addMaintenanceRecord = () => {
|
|
|
+ const newRecord = {
|
|
|
+ id: Date.now(),
|
|
|
+ date: maintenanceForm.date,
|
|
|
+ type: maintenanceForm.type,
|
|
|
+ description: maintenanceForm.description,
|
|
|
+ cost: maintenanceForm.cost,
|
|
|
+ status: '已完成',
|
|
|
+ }
|
|
|
+
|
|
|
+ maintenanceRecords.value.unshift(newRecord)
|
|
|
+
|
|
|
+ Object.keys(maintenanceForm).forEach((key) => {
|
|
|
+ maintenanceForm[key] = typeof maintenanceForm[key] === 'number' ? 0 : ''
|
|
|
+ })
|
|
|
+
|
|
|
+ showAddMaintenanceDialog.value = false
|
|
|
+ ElMessage.success('维护记录添加成功!')
|
|
|
+}
|
|
|
+
|
|
|
+const viewMaintenanceDetail = (record: any) => {
|
|
|
+ ElMessage.info(`查看维护记录:${record.description}`)
|
|
|
+}
|
|
|
+
|
|
|
+const calculateEndDate = (startDate: string, term: string) => {
|
|
|
+ const start = new Date(startDate)
|
|
|
+ const months = term === '6个月' ? 6 : term === '1年' ? 12 : term === '2年' ? 24 : 36
|
|
|
+ start.setMonth(start.getMonth() + months)
|
|
|
+ return start.toISOString().split('T')[0]
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ // 组件挂载后的初始化逻辑
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.el-card {
|
|
|
+ border: none;
|
|
|
+}
|
|
|
+
|
|
|
+.el-button {
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.el-steps {
|
|
|
+ margin-bottom: 2rem;
|
|
|
+}
|
|
|
+
|
|
|
+/* AR看房按钮特殊样式 */
|
|
|
+.ar-viewing-btn {
|
|
|
+ position: relative;
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ border: none;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+}
|
|
|
+
|
|
|
+.ar-viewing-btn:hover {
|
|
|
+ transform: translateY(-2px) scale(1.05);
|
|
|
+ box-shadow: 0 20px 40px rgba(102, 126, 234, 0.4);
|
|
|
+}
|
|
|
+
|
|
|
+.ar-viewing-btn:active {
|
|
|
+ transform: translateY(0) scale(0.98);
|
|
|
+}
|
|
|
+
|
|
|
+.ar-viewing-btn::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 50%, #4facfe 100%);
|
|
|
+ border-radius: inherit;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
+ z-index: -1;
|
|
|
+}
|
|
|
+
|
|
|
+.ar-viewing-btn:hover::before {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+/* 渐变背景动画 */
|
|
|
+@keyframes gradient {
|
|
|
+ 0% {
|
|
|
+ background-position: 0% 50%;
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ background-position: 100% 50%;
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ background-position: 0% 50%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.bg-gradient-to-r {
|
|
|
+ background-size: 200% 200%;
|
|
|
+ animation: gradient 15s ease infinite;
|
|
|
+}
|
|
|
+
|
|
|
+/* 脉冲动画 */
|
|
|
+@keyframes ping {
|
|
|
+ 75%,
|
|
|
+ 100% {
|
|
|
+ transform: scale(2);
|
|
|
+ opacity: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.animate-ping {
|
|
|
+ animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
|
|
+}
|
|
|
+</style>
|