初始化
This commit is contained in:
commit
cf9305fd93
|
|
@ -0,0 +1,16 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/essential'
|
||||
],
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
},
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
dist/
|
||||
.idea/
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>租房管理系统</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but rentease-web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "rentease-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"element-ui": "^2.15.6",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-eslint": "^5.0.9",
|
||||
"@vue/cli-plugin-router": "^5.0.9",
|
||||
"@vue/cli-service": "^5.0.9",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^7.14.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<el-container>
|
||||
<el-header height="60px" style="background-color: #333; color: white; display: flex; align-items: center; justify-content: space-between;">
|
||||
<h1 style="margin: 0; font-size: 18px;">租房管理系统</h1>
|
||||
<el-button type="primary" plain @click="logout">退出</el-button>
|
||||
</el-header>
|
||||
<el-container>
|
||||
<el-aside width="200px" style="background-color: #f0f2f5; min-height: calc(100vh - 60px);">
|
||||
<el-menu
|
||||
:default-active="activeIndex"
|
||||
class="el-menu-vertical-demo"
|
||||
@select="handleSelect"
|
||||
background-color="#f0f2f5"
|
||||
text-color="#333"
|
||||
active-text-color="#409EFF"
|
||||
>
|
||||
<el-menu-item index="dashboard">
|
||||
<i class="el-icon-s-home"></i>
|
||||
<span>首页</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="region-list">
|
||||
<i class="el-icon-location"></i>
|
||||
<span>区域管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="apartment-list">
|
||||
<i class="el-icon-office-building"></i>
|
||||
<span>公寓管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="room-list">
|
||||
<i class="el-icon-menu"></i>
|
||||
<span>房间管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="rental-list">
|
||||
<i class="el-icon-key"></i>
|
||||
<span>租房管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="tenant-list">
|
||||
<i class="el-icon-user"></i>
|
||||
<span>租客档案</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="contract-list">
|
||||
<i class="el-icon-document"></i>
|
||||
<span>合同档案</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="rent-statistics">
|
||||
<i class="el-icon-data-analysis"></i>
|
||||
<span>租金统计</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="room-statistics">
|
||||
<i class="el-icon-data-analysis"></i>
|
||||
<span>房间状态统计</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-main style="padding: 20px;">
|
||||
<router-view />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App',
|
||||
data() {
|
||||
return {
|
||||
activeIndex: 'dashboard'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSelect(key, keyPath) {
|
||||
this.activeIndex = key
|
||||
// 根据选择的菜单项导航到对应的路由
|
||||
const routeMap = {
|
||||
'dashboard': '/',
|
||||
'region-list': '/region/list',
|
||||
'region-add': '/region/add',
|
||||
'apartment-list': '/apartment/list',
|
||||
'apartment-add': '/apartment/add',
|
||||
'room-list': '/room/list',
|
||||
'room-add': '/room/add',
|
||||
'rental-list': '/rental/list',
|
||||
'rental-add': '/rental/add',
|
||||
'tenant-list': '/tenant/list',
|
||||
'tenant-add': '/tenant/add',
|
||||
'contract-list': '/contract/list',
|
||||
'contract-add': '/contract/add',
|
||||
'rent-statistics': '/statistics/rent',
|
||||
'room-statistics': '/statistics/room'
|
||||
}
|
||||
if (routeMap[key] && this.$route.path !== routeMap[key]) {
|
||||
this.$router.push(routeMap[key])
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
// 退出登录逻辑
|
||||
this.$message.success('退出成功')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.el-menu-vertical-demo:not(.el-menu--collapse) {
|
||||
width: 200px;
|
||||
min-height: 400px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
// API服务层,用于与后端进行交互
|
||||
|
||||
const API_BASE_URL = 'http://localhost:3000/api';
|
||||
|
||||
// 通用请求函数
|
||||
async function request(url, options = {}) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}${url}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('API request error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 区域管理API
|
||||
export const regionApi = {
|
||||
getAll: () => request('/regions'),
|
||||
getById: (id) => request(`/regions/${id}`),
|
||||
create: (data) => request('/regions', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
update: (id, data) => request(`/regions/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
delete: (id) => request(`/regions/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
};
|
||||
|
||||
// 构建查询字符串
|
||||
function buildQueryString(params) {
|
||||
const query = Object.entries(params)
|
||||
.filter(([key, value]) => value !== undefined && value !== null && value !== '')
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
.join('&');
|
||||
return query ? `?${query}` : '';
|
||||
}
|
||||
|
||||
// 公寓管理API
|
||||
export const apartmentApi = {
|
||||
getAll: (params = {}) => request(`/apartments${buildQueryString(params)}`),
|
||||
getById: (id) => request(`/apartments/${id}`),
|
||||
create: (data) => request('/apartments', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
update: (id, data) => request(`/apartments/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
delete: (id) => request(`/apartments/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
};
|
||||
|
||||
// 房间管理API
|
||||
export const roomApi = {
|
||||
getAll: (params = {}) => request(`/rooms${buildQueryString(params)}`),
|
||||
getById: (id) => request(`/rooms/${id}`),
|
||||
create: (data) => request('/rooms', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
update: (id, data) => request(`/rooms/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
delete: (id) => request(`/rooms/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
};
|
||||
|
||||
// 租客管理API
|
||||
export const tenantApi = {
|
||||
getAll: () => request('/tenants'),
|
||||
getById: (id) => request(`/tenants/${id}`),
|
||||
create: (data) => request('/tenants', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
update: (id, data) => request(`/tenants/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
delete: (id) => request(`/tenants/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
};
|
||||
|
||||
// 合同管理API
|
||||
export const contractApi = {
|
||||
getAll: () => request('/contracts'),
|
||||
getById: (id) => request(`/contracts/${id}`),
|
||||
create: (data) => request('/contracts', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
update: (id, data) => request(`/contracts/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
delete: (id) => request(`/contracts/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
};
|
||||
|
||||
// 租房管理API
|
||||
export const rentalApi = {
|
||||
getAll: (params = {}) => request(`/rentals${buildQueryString(params)}`),
|
||||
getById: (id) => request(`/rentals/${id}`),
|
||||
create: (data) => request('/rentals', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
update: (id, data) => request(`/rentals/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
delete: (id) => request(`/rentals/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
};
|
||||
|
||||
// 统计分析API
|
||||
export const statisticsApi = {
|
||||
getRentData: () => request('/statistics/rent'),
|
||||
getRoomStatus: () => request('/statistics/room-status'),
|
||||
getRegionHouseStats: () => request('/statistics/region-house'),
|
||||
getRegionApartmentHouseStats: () => request('/statistics/region-apartment-house')
|
||||
};
|
||||
|
||||
// 水费管理API
|
||||
export const waterBillApi = {
|
||||
getAll: (params = {}) => request(`/water-bills${buildQueryString(params)}`),
|
||||
getById: (id) => request(`/water-bills/${id}`),
|
||||
create: (data) => request('/water-bills', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
update: (id, data) => request(`/water-bills/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
delete: (id) => request(`/water-bills/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
};
|
||||
|
||||
// 电费管理API
|
||||
export const electricityBillApi = {
|
||||
getAll: (params = {}) => request(`/electricity-bills${buildQueryString(params)}`),
|
||||
getById: (id) => request(`/electricity-bills/${id}`),
|
||||
create: (data) => request('/electricity-bills', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
update: (id, data) => request(`/electricity-bills/${id}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
}),
|
||||
delete: (id) => request(`/electricity-bills/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
};
|
||||
|
||||
export default {
|
||||
region: regionApi,
|
||||
apartment: apartmentApi,
|
||||
room: roomApi,
|
||||
tenant: tenantApi,
|
||||
contract: contractApi,
|
||||
rental: rentalApi,
|
||||
statistics: statisticsApi,
|
||||
waterBill: waterBillApi,
|
||||
electricityBill: electricityBillApi
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import ElementUI from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
|
||||
Vue.use(ElementUI)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
// 模拟数据
|
||||
|
||||
// 区域数据
|
||||
export const regions = [
|
||||
{ id: 1, name: '大商汇', description: '大商汇区域', createTime: '2023-01-06' },
|
||||
{ id: 2, name: '丰源市场', description: '丰源市场区域', createTime: '2023-01-07' }
|
||||
]
|
||||
|
||||
// 房间状态
|
||||
const ROOM_STATUS = {
|
||||
EMPTY: 'empty', // 空房(白色)
|
||||
RENTED: 'rented', // 在租(绿色)
|
||||
SOON_EXPIRE: 'soon_expire', // 即将到期(黄色)
|
||||
EXPIRED: 'expired', // 已到期(红色)
|
||||
CLEANING: 'cleaning', // 打扫中(灰色)
|
||||
MAINTENANCE: 'maintenance' // 维修中(黑色)
|
||||
}
|
||||
|
||||
// 公寓数据
|
||||
export const apartments = [
|
||||
{ id: 1, regionId: 1, name: '爱奇艺公寓', address: '大商汇区域', createTime: '2023-01-18' },
|
||||
{ id: 2, regionId: 2, name: '碧云公寓', address: '丰源市场区域', createTime: '2023-01-19' }
|
||||
]
|
||||
|
||||
// 房间数据
|
||||
export const rooms = [
|
||||
// 大商汇 - 爱奇艺公寓
|
||||
{ id: 1, apartmentId: 1, roomNumber: '401', area: 40, price: 2500, status: ROOM_STATUS.EMPTY, createTime: '2023-01-18' },
|
||||
{ id: 2, apartmentId: 1, roomNumber: '402', area: 40, price: 2500, status: ROOM_STATUS.EMPTY, createTime: '2023-01-18' },
|
||||
{ id: 3, apartmentId: 1, roomNumber: '403', area: 45, price: 2800, status: ROOM_STATUS.RENTED, createTime: '2023-01-18' },
|
||||
{ id: 4, apartmentId: 1, roomNumber: '404', area: 45, price: 2800, status: ROOM_STATUS.SOON_EXPIRE, createTime: '2023-01-18' },
|
||||
{ id: 5, apartmentId: 1, roomNumber: '405', area: 50, price: 3000, status: ROOM_STATUS.EMPTY, createTime: '2023-01-18' },
|
||||
{ id: 6, apartmentId: 1, roomNumber: '406', area: 50, price: 3000, status: ROOM_STATUS.EXPIRED, createTime: '2023-01-18' },
|
||||
{ id: 7, apartmentId: 1, roomNumber: '407', area: 55, price: 3200, status: ROOM_STATUS.CLEANING, createTime: '2023-01-18' },
|
||||
// 丰源市场 - 碧云公寓
|
||||
{ id: 8, apartmentId: 2, roomNumber: '201', area: 35, price: 2200, status: ROOM_STATUS.EMPTY, createTime: '2023-01-19' },
|
||||
{ id: 9, apartmentId: 2, roomNumber: '202', area: 35, price: 2200, status: ROOM_STATUS.RENTED, createTime: '2023-01-19' },
|
||||
{ id: 10, apartmentId: 2, roomNumber: '203', area: 40, price: 2400, status: ROOM_STATUS.EMPTY, createTime: '2023-01-19' },
|
||||
{ id: 11, apartmentId: 2, roomNumber: '205', area: 40, price: 2400, status: ROOM_STATUS.MAINTENANCE, createTime: '2023-01-19' },
|
||||
{ id: 12, apartmentId: 2, roomNumber: '206', area: 45, price: 2600, status: ROOM_STATUS.RENTED, createTime: '2023-01-19' },
|
||||
{ id: 13, apartmentId: 2, roomNumber: '208', area: 45, price: 2600, status: ROOM_STATUS.EMPTY, createTime: '2023-01-19' },
|
||||
{ id: 14, apartmentId: 2, roomNumber: '209', area: 50, price: 2800, status: ROOM_STATUS.SOON_EXPIRE, createTime: '2023-01-19' }
|
||||
]
|
||||
|
||||
// 租客数据
|
||||
export const tenants = [
|
||||
{ id: 1, name: '张三', phone: '13800138001', idCard: '110101199001011234', createTime: '2023-02-01' },
|
||||
{ id: 2, name: '李四', phone: '13800138002', idCard: '110101199001011235', createTime: '2023-02-02' },
|
||||
{ id: 3, name: '王五', phone: '13800138003', idCard: '110101199001011236', createTime: '2023-02-03' },
|
||||
{ id: 4, name: '赵六', phone: '13800138004', idCard: '110101199001011237', createTime: '2023-02-04' },
|
||||
{ id: 5, name: '钱七', phone: '13800138005', idCard: '110101199001011238', createTime: '2023-02-05' }
|
||||
]
|
||||
|
||||
// 合同数据
|
||||
export const contracts = [
|
||||
{ id: 1, houseId: 2, tenantId: 1, startDate: '2023-03-01', endDate: '2024-03-01', rent: 8000, deposit: 16000, status: 'active', createTime: '2023-02-28' },
|
||||
{ id: 2, houseId: 5, tenantId: 2, startDate: '2023-04-01', endDate: '2024-04-01', rent: 5500, deposit: 11000, status: 'active', createTime: '2023-03-31' },
|
||||
{ id: 3, houseId: 8, tenantId: 3, startDate: '2023-05-01', endDate: '2024-05-01', rent: 5800, deposit: 11600, status: 'active', createTime: '2023-04-30' },
|
||||
{ id: 4, houseId: 2, tenantId: 4, startDate: '2022-03-01', endDate: '2023-03-01', rent: 7500, deposit: 15000, status: 'expired', createTime: '2022-02-28' },
|
||||
{ id: 5, houseId: 5, tenantId: 5, startDate: '2022-04-01', endDate: '2023-04-01', rent: 5000, deposit: 10000, status: 'expired', createTime: '2022-03-31' }
|
||||
]
|
||||
|
||||
// 租房数据
|
||||
export const rentals = [
|
||||
{ id: 1, roomId: 3, tenantId: 1, contractId: 1, startDate: '2023-03-01', endDate: '2024-03-01', rent: 2800, deposit: 5600, status: 'active', createTime: '2023-02-28' },
|
||||
{ id: 2, roomId: 9, tenantId: 2, contractId: 2, startDate: '2023-04-01', endDate: '2024-04-01', rent: 2200, deposit: 4400, status: 'active', createTime: '2023-03-31' },
|
||||
{ id: 3, roomId: 12, tenantId: 3, contractId: 3, startDate: '2023-05-01', endDate: '2024-05-01', rent: 2600, deposit: 5200, status: 'active', createTime: '2023-04-30' },
|
||||
{ id: 4, roomId: 4, tenantId: 4, contractId: 4, startDate: '2022-03-01', endDate: '2023-03-01', rent: 2800, deposit: 5600, status: 'expired', createTime: '2022-02-28' },
|
||||
{ id: 5, roomId: 6, tenantId: 5, contractId: 5, startDate: '2022-04-01', endDate: '2023-04-01', rent: 3000, deposit: 6000, status: 'expired', createTime: '2022-03-31' }
|
||||
]
|
||||
|
||||
// 租金统计数据
|
||||
export const rentStatistics = [
|
||||
{ month: '2023-01', amount: 25000 },
|
||||
{ month: '2023-02', amount: 28000 },
|
||||
{ month: '2023-03', amount: 30000 },
|
||||
{ month: '2023-04', amount: 32000 },
|
||||
{ month: '2023-05', amount: 35000 },
|
||||
{ month: '2023-06', amount: 38000 }
|
||||
]
|
||||
|
||||
// 房间状态统计数据
|
||||
export const roomStatusStatistics = [
|
||||
{ status: '空房', count: 7 },
|
||||
{ status: '在租', count: 3 },
|
||||
{ status: '即将到期', count: 2 },
|
||||
{ status: '已到期', count: 1 },
|
||||
{ status: '打扫中', count: 1 },
|
||||
{ status: '维修中', count: 0 }
|
||||
]
|
||||
|
||||
// 区域房屋统计数据
|
||||
export const regionHouseStatistics = [
|
||||
{ region: '大商汇', empty: 3, rented: 1, soon_expire: 1, expired: 1, cleaning: 1, maintenance: 0, total: 7 },
|
||||
{ region: '丰源市场', empty: 4, rented: 2, soon_expire: 1, expired: 0, cleaning: 0, maintenance: 0, total: 7 }
|
||||
]
|
||||
|
||||
// 导出常量
|
||||
export const CONSTANTS = {
|
||||
ROOM_STATUS
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Dashboard',
|
||||
component: () => import('../views/Dashboard.vue')
|
||||
},
|
||||
// 区域管理
|
||||
{
|
||||
path: '/region/list',
|
||||
name: 'RegionList',
|
||||
component: () => import('../views/region/List.vue')
|
||||
},
|
||||
{
|
||||
path: '/region/add',
|
||||
name: 'RegionAdd',
|
||||
component: () => import('../views/region/Add.vue')
|
||||
},
|
||||
{
|
||||
path: '/region/edit/:id',
|
||||
name: 'RegionEdit',
|
||||
component: () => import('../views/region/Edit.vue')
|
||||
},
|
||||
// 公寓管理
|
||||
{
|
||||
path: '/apartment/list',
|
||||
name: 'ApartmentList',
|
||||
component: () => import('../views/apartment/List.vue')
|
||||
},
|
||||
{
|
||||
path: '/apartment/add',
|
||||
name: 'ApartmentAdd',
|
||||
component: () => import('../views/apartment/Add.vue')
|
||||
},
|
||||
{
|
||||
path: '/apartment/edit/:id',
|
||||
name: 'ApartmentEdit',
|
||||
component: () => import('../views/apartment/Edit.vue')
|
||||
},
|
||||
// 房间管理
|
||||
{
|
||||
path: '/room/list',
|
||||
name: 'RoomList',
|
||||
component: () => import('../views/room/List.vue')
|
||||
},
|
||||
{
|
||||
path: '/room/add',
|
||||
name: 'RoomAdd',
|
||||
component: () => import('../views/room/Add.vue')
|
||||
},
|
||||
{
|
||||
path: '/room/edit/:id',
|
||||
name: 'RoomEdit',
|
||||
component: () => import('../views/room/Edit.vue')
|
||||
},
|
||||
// 租房管理
|
||||
{
|
||||
path: '/rental/list',
|
||||
name: 'RentalList',
|
||||
component: () => import('../views/rental/List.vue')
|
||||
},
|
||||
{
|
||||
path: '/rental/add',
|
||||
name: 'RentalAdd',
|
||||
component: () => import('../views/rental/Add.vue')
|
||||
},
|
||||
{
|
||||
path: '/rental/edit/:id',
|
||||
name: 'RentalEdit',
|
||||
component: () => import('../views/rental/Edit.vue')
|
||||
},
|
||||
{
|
||||
path: '/rental/detail/:id',
|
||||
name: 'RentalDetail',
|
||||
component: () => import('../views/rental/Detail.vue')
|
||||
},
|
||||
// 租客管理
|
||||
{
|
||||
path: '/tenant/list',
|
||||
name: 'TenantList',
|
||||
component: () => import('../views/tenant/List.vue')
|
||||
},
|
||||
{
|
||||
path: '/tenant/add',
|
||||
name: 'TenantAdd',
|
||||
component: () => import('../views/tenant/Add.vue')
|
||||
},
|
||||
{
|
||||
path: '/tenant/edit/:id',
|
||||
name: 'TenantEdit',
|
||||
component: () => import('../views/tenant/Edit.vue')
|
||||
},
|
||||
// 合同管理
|
||||
{
|
||||
path: '/contract/list',
|
||||
name: 'ContractList',
|
||||
component: () => import('../views/contract/List.vue')
|
||||
},
|
||||
{
|
||||
path: '/contract/add',
|
||||
name: 'ContractAdd',
|
||||
component: () => import('../views/contract/Add.vue')
|
||||
},
|
||||
{
|
||||
path: '/contract/edit/:id',
|
||||
name: 'ContractEdit',
|
||||
component: () => import('../views/contract/Edit.vue')
|
||||
},
|
||||
// 统计分析
|
||||
{
|
||||
path: '/statistics/rent',
|
||||
name: 'RentStatistics',
|
||||
component: () => import('../views/statistics/Rent.vue')
|
||||
},
|
||||
{
|
||||
path: '/statistics/room',
|
||||
name: 'RoomStatistics',
|
||||
component: () => import('../views/statistics/House.vue')
|
||||
}
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
routes
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
<template>
|
||||
<div class="dashboard">
|
||||
<el-card class="welcome-card">
|
||||
<h2>欢迎使用租房管理系统</h2>
|
||||
<p>本系统提供区域管理、房源管理、租客管理、合同管理和统计分析等功能</p>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="20" style="margin-top: 20px;">
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-item">
|
||||
<el-icon class="el-icon-location"></el-icon>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ regionCount }}</div>
|
||||
<div class="stat-label">区域数量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-item">
|
||||
<el-icon class="el-icon-building"></el-icon>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ apartmentCount }}</div>
|
||||
<div class="stat-label">公寓数量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-item">
|
||||
<el-icon class="el-icon-home"></el-icon>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ roomCount }}</div>
|
||||
<div class="stat-label">房间数量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-item">
|
||||
<el-icon class="el-icon-user"></el-icon>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ tenantCount }}</div>
|
||||
<div class="stat-label">租客数量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-item">
|
||||
<el-icon class="el-icon-document"></el-icon>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ contractCount }}</div>
|
||||
<div class="stat-label">合同数量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-item">
|
||||
<el-icon class="el-icon-s-finance"></el-icon>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ emptyRoomCount }}</div>
|
||||
<div class="stat-label">空房数量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="8">
|
||||
<el-card class="stat-card">
|
||||
<div class="stat-item">
|
||||
<el-icon class="el-icon-s-finance"></el-icon>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ rentedRoomCount }}</div>
|
||||
<div class="stat-label">在租数量</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-card style="margin-top: 20px;">
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>区域公寓房间状态分布</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="regionApartmentHouseStats" style="width: 100%">
|
||||
<el-table-column prop="region" label="区域" width="180"></el-table-column>
|
||||
<el-table-column prop="apartment" label="公寓" width="180"></el-table-column>
|
||||
<el-table-column prop="empty" label="空房" width="80"></el-table-column>
|
||||
<el-table-column prop="rented" label="在租" width="80"></el-table-column>
|
||||
<el-table-column prop="soon_expire" label="即将到期" width="100"></el-table-column>
|
||||
<el-table-column prop="expired" label="已到期" width="80"></el-table-column>
|
||||
<el-table-column prop="cleaning" label="打扫中" width="80"></el-table-column>
|
||||
<el-table-column prop="maintenance" label="维修中" width="80"></el-table-column>
|
||||
<el-table-column prop="total" label="总数" width="80"></el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { regionApi, apartmentApi, roomApi, tenantApi, contractApi, statisticsApi } from '../api/api'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
data() {
|
||||
return {
|
||||
regionCount: 0,
|
||||
apartmentCount: 0,
|
||||
roomCount: 0,
|
||||
tenantCount: 0,
|
||||
contractCount: 0,
|
||||
emptyRoomCount: 0,
|
||||
rentedRoomCount: 0,
|
||||
regionApartmentHouseStats: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
try {
|
||||
// 并行加载所有数据
|
||||
const [regionsResponse, apartmentsResponse, roomsResponse, tenantsResponse, contractsResponse, regionApartmentHouseStatsResponse] = await Promise.all([
|
||||
regionApi.getAll(),
|
||||
apartmentApi.getAll(),
|
||||
roomApi.getAll(),
|
||||
tenantApi.getAll(),
|
||||
contractApi.getAll(),
|
||||
statisticsApi.getRegionApartmentHouseStats()
|
||||
])
|
||||
|
||||
// 处理不同格式的响应数据
|
||||
const regions = regionsResponse.data || regionsResponse
|
||||
const apartments = apartmentsResponse.data || apartmentsResponse
|
||||
const rooms = roomsResponse.data || roomsResponse
|
||||
const tenants = tenantsResponse.data || tenantsResponse
|
||||
const contracts = contractsResponse.data || contractsResponse
|
||||
|
||||
this.regionCount = regions.length
|
||||
this.apartmentCount = apartments.length
|
||||
this.roomCount = rooms.length
|
||||
this.tenantCount = tenants.length
|
||||
this.contractCount = contracts.length
|
||||
this.emptyRoomCount = rooms.filter(room => room.status === 'empty').length
|
||||
this.rentedRoomCount = rooms.filter(room => room.status === 'rented').length
|
||||
this.regionApartmentHouseStats = regionApartmentHouseStatsResponse
|
||||
} catch (error) {
|
||||
this.$message.error('加载数据失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.stat-item .el-icon {
|
||||
font-size: 36px;
|
||||
color: #409EFF;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="apartment-add">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>添加公寓</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="apartmentForm" :rules="rules" ref="apartmentForm" label-width="100px">
|
||||
<el-form-item label="区域" prop="regionId">
|
||||
<el-select v-model="apartmentForm.regionId" placeholder="请选择区域">
|
||||
<el-option v-for="region in regions" :key="region.id" :label="region.name" :value="region.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="公寓名称" prop="name">
|
||||
<el-input v-model="apartmentForm.name" placeholder="请输入公寓名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="apartmentForm.address" placeholder="请输入地址"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { apartmentApi, regionApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'ApartmentAdd',
|
||||
data() {
|
||||
return {
|
||||
apartmentForm: {
|
||||
regionId: '',
|
||||
name: '',
|
||||
address: ''
|
||||
},
|
||||
regions: [],
|
||||
rules: {
|
||||
regionId: [
|
||||
{ required: true, message: '请选择区域', trigger: 'blur' }
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: '请输入公寓名称', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
address: [
|
||||
{ required: true, message: '请输入公寓地址', trigger: 'blur' },
|
||||
{ min: 5, max: 100, message: '长度在 5 到 100 个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadRegions()
|
||||
},
|
||||
methods: {
|
||||
async loadRegions() {
|
||||
try {
|
||||
const response = await regionApi.getAll()
|
||||
this.regions = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载区域数据失败')
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.apartmentForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await apartmentApi.create(this.apartmentForm)
|
||||
this.$message.success('添加成功')
|
||||
this.$router.push('/apartment/list')
|
||||
} catch (error) {
|
||||
this.$message.error('添加失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.$refs.apartmentForm.resetFields()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/apartment/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.apartment-add {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<template>
|
||||
<div class="apartment-edit">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>编辑公寓</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="apartmentForm" :rules="rules" ref="apartmentForm" label-width="100px">
|
||||
<el-form-item label="区域" prop="regionId">
|
||||
<el-select v-model="apartmentForm.regionId" placeholder="请选择区域">
|
||||
<el-option v-for="region in regions" :key="region.id" :label="region.name" :value="region.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="公寓名称" prop="name">
|
||||
<el-input v-model="apartmentForm.name" placeholder="请输入公寓名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="apartmentForm.address" placeholder="请输入地址"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { apartmentApi, regionApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'ApartmentEdit',
|
||||
data() {
|
||||
return {
|
||||
apartmentForm: {
|
||||
id: '',
|
||||
regionId: '',
|
||||
name: '',
|
||||
address: ''
|
||||
},
|
||||
regions: [],
|
||||
rules: {
|
||||
regionId: [
|
||||
{ required: true, message: '请选择区域', trigger: 'blur' }
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: '请输入公寓名称', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
address: [
|
||||
{ required: true, message: '请输入公寓地址', trigger: 'blur' },
|
||||
{ min: 5, max: 100, message: '长度在 5 到 100 个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadRegions()
|
||||
this.loadApartmentData()
|
||||
},
|
||||
methods: {
|
||||
async loadRegions() {
|
||||
try {
|
||||
const response = await regionApi.getAll()
|
||||
this.regions = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载区域数据失败')
|
||||
}
|
||||
},
|
||||
async loadApartmentData() {
|
||||
try {
|
||||
const id = this.$route.params.id
|
||||
const apartment = await apartmentApi.getById(id)
|
||||
if (apartment) {
|
||||
this.apartmentForm = apartment
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载公寓数据失败')
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.apartmentForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await apartmentApi.update(this.apartmentForm.id, this.apartmentForm)
|
||||
this.$message.success('编辑成功')
|
||||
this.$router.push('/apartment/list')
|
||||
} catch (error) {
|
||||
this.$message.error('编辑失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.loadApartmentData()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/apartment/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.apartment-edit {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
<template>
|
||||
<div class="apartment-list">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>公寓列表</span>
|
||||
<el-button type="primary" @click="handleAdd">添加公寓</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="区域">
|
||||
<el-select v-model="searchForm.regionId" placeholder="请选择区域">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option v-for="region in regions" :key="region.id" :label="region.name" :value="region.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="公寓名称">
|
||||
<el-input v-model="searchForm.name" placeholder="请输入公寓名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="apartments" style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||||
<el-table-column prop="regionName" label="区域"></el-table-column>
|
||||
<el-table-column prop="name" label="公寓名称"></el-table-column>
|
||||
<el-table-column prop="address" label="地址"></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="small" type="primary" @click="handleEdit(scope.row.id)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination" style="margin-top: 20px;">
|
||||
<el-pagination
|
||||
layout="prev, pager, next"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page="currentPage"
|
||||
@current-change="handleCurrentChange"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { apartmentApi, regionApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'ApartmentList',
|
||||
data() {
|
||||
return {
|
||||
regions: [],
|
||||
apartments: [],
|
||||
total: 0,
|
||||
searchForm: {
|
||||
regionId: '',
|
||||
name: ''
|
||||
},
|
||||
currentPage: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
try {
|
||||
// 加载区域数据
|
||||
const regionsResponse = await regionApi.getAll()
|
||||
this.regions = regionsResponse
|
||||
|
||||
// 构建查询参数
|
||||
const params = {
|
||||
regionId: this.searchForm.regionId,
|
||||
name: this.searchForm.name,
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize
|
||||
}
|
||||
|
||||
// 加载公寓数据
|
||||
const apartmentsResponse = await apartmentApi.getAll(params)
|
||||
this.apartments = apartmentsResponse.data.map(apartment => {
|
||||
const region = regionsResponse.find(r => r.id == apartment.regionId)
|
||||
return {
|
||||
...apartment,
|
||||
regionName: region ? region.name : ''
|
||||
}
|
||||
})
|
||||
this.total = apartmentsResponse.total
|
||||
} catch (error) {
|
||||
this.$message.error('加载数据失败')
|
||||
}
|
||||
},
|
||||
handleAdd() {
|
||||
this.$router.push('/apartment/add')
|
||||
},
|
||||
handleEdit(id) {
|
||||
this.$router.push(`/apartment/edit/${id}`)
|
||||
},
|
||||
async handleDelete(id) {
|
||||
this.$confirm('确定要删除这个公寓吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
await apartmentApi.delete(id)
|
||||
this.$message.success('删除成功')
|
||||
this.loadData()
|
||||
} catch (error) {
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消删除
|
||||
})
|
||||
},
|
||||
handleSearch() {
|
||||
this.currentPage = 1
|
||||
this.loadData()
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm = {
|
||||
regionId: '',
|
||||
name: ''
|
||||
}
|
||||
this.currentPage = 1
|
||||
this.loadData()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.apartment-list {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<div class="contract-add">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>添加合同</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="contractForm" :rules="rules" ref="contractForm" label-width="120px">
|
||||
<el-form-item label="房间" prop="roomId">
|
||||
<el-select v-model="contractForm.roomId" placeholder="请选择房间">
|
||||
<el-option v-for="room in rooms" :key="room.id" :label="room.roomNumber" :value="room.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="租客" prop="tenantId">
|
||||
<el-select v-model="contractForm.tenantId" placeholder="请选择租客">
|
||||
<el-option v-for="tenant in tenants" :key="tenant.id" :label="tenant.name" :value="tenant.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期" prop="startDate">
|
||||
<el-date-picker v-model="contractForm.startDate" type="date" placeholder="选择开始日期" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期" prop="endDate">
|
||||
<el-date-picker v-model="contractForm.endDate" type="date" placeholder="选择结束日期" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="租金(元/月)" prop="rent">
|
||||
<el-input type="number" v-model="contractForm.rent" placeholder="请输入租金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="押金(元)" prop="deposit">
|
||||
<el-input type="number" v-model="contractForm.deposit" placeholder="请输入押金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { contractApi, tenantApi, roomApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'ContractAdd',
|
||||
data() {
|
||||
return {
|
||||
contractForm: {
|
||||
houseId: '',
|
||||
tenantId: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
rent: '',
|
||||
deposit: ''
|
||||
},
|
||||
tenants: [],
|
||||
rooms: [],
|
||||
rules: {
|
||||
houseId: [
|
||||
{ required: true, message: '请选择房间', trigger: 'blur' }
|
||||
],
|
||||
tenantId: [
|
||||
{ required: true, message: '请选择租客', trigger: 'blur' }
|
||||
],
|
||||
startDate: [
|
||||
{ required: true, message: '请选择开始日期', trigger: 'blur' }
|
||||
],
|
||||
endDate: [
|
||||
{ required: true, message: '请选择结束日期', trigger: 'blur' }
|
||||
],
|
||||
rent: [
|
||||
{ required: true, message: '请输入租金', trigger: 'blur' },
|
||||
{ type: 'number', message: '请输入数字', trigger: 'blur' }
|
||||
],
|
||||
deposit: [
|
||||
{ required: true, message: '请输入押金', trigger: 'blur' },
|
||||
{ type: 'number', message: '请输入数字', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadTenants()
|
||||
this.loadRooms()
|
||||
},
|
||||
methods: {
|
||||
async loadTenants() {
|
||||
try {
|
||||
const response = await tenantApi.getAll()
|
||||
this.tenants = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载租客数据失败')
|
||||
}
|
||||
},
|
||||
async loadRooms() {
|
||||
try {
|
||||
const response = await roomApi.getAll()
|
||||
this.rooms = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载房间数据失败')
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.contractForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await contractApi.create(this.contractForm)
|
||||
this.$message.success('添加成功')
|
||||
this.$router.push('/contract/list')
|
||||
} catch (error) {
|
||||
this.$message.error('添加失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.$refs.contractForm.resetFields()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/contract/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.contract-add {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<div class="contract-edit">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>编辑合同</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="contractForm" :rules="rules" ref="contractForm" label-width="120px">
|
||||
<el-form-item label="房间" prop="roomId">
|
||||
<el-select v-model="contractForm.roomId" placeholder="请选择房间">
|
||||
<el-option v-for="room in rooms" :key="room.id" :label="room.roomNumber" :value="room.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="租客" prop="tenantId">
|
||||
<el-select v-model="contractForm.tenantId" placeholder="请选择租客">
|
||||
<el-option v-for="tenant in tenants" :key="tenant.id" :label="tenant.name" :value="tenant.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期" prop="startDate">
|
||||
<el-date-picker v-model="contractForm.startDate" type="date" placeholder="选择开始日期" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期" prop="endDate">
|
||||
<el-date-picker v-model="contractForm.endDate" type="date" placeholder="选择结束日期" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="租金(元/月)" prop="rent">
|
||||
<el-input type="number" v-model="contractForm.rent" placeholder="请输入租金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="押金(元)" prop="deposit">
|
||||
<el-input type="number" v-model="contractForm.deposit" placeholder="请输入押金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { contractApi, tenantApi, roomApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'ContractEdit',
|
||||
data() {
|
||||
return {
|
||||
contractForm: {
|
||||
id: '',
|
||||
houseId: '',
|
||||
tenantId: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
rent: '',
|
||||
deposit: ''
|
||||
},
|
||||
tenants: [],
|
||||
rooms: [],
|
||||
rules: {
|
||||
houseId: [
|
||||
{ required: true, message: '请选择房间', trigger: 'blur' }
|
||||
],
|
||||
tenantId: [
|
||||
{ required: true, message: '请选择租客', trigger: 'blur' }
|
||||
],
|
||||
startDate: [
|
||||
{ required: true, message: '请选择开始日期', trigger: 'blur' }
|
||||
],
|
||||
endDate: [
|
||||
{ required: true, message: '请选择结束日期', trigger: 'blur' }
|
||||
],
|
||||
rent: [
|
||||
{ required: true, message: '请输入租金', trigger: 'blur' },
|
||||
{ type: 'number', message: '请输入数字', trigger: 'blur' }
|
||||
],
|
||||
deposit: [
|
||||
{ required: true, message: '请输入押金', trigger: 'blur' },
|
||||
{ type: 'number', message: '请输入数字', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadTenants()
|
||||
this.loadRooms()
|
||||
this.loadContractData()
|
||||
},
|
||||
methods: {
|
||||
async loadTenants() {
|
||||
try {
|
||||
const response = await tenantApi.getAll()
|
||||
this.tenants = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载租客数据失败')
|
||||
}
|
||||
},
|
||||
async loadRooms() {
|
||||
try {
|
||||
const response = await roomApi.getAll()
|
||||
this.rooms = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载房间数据失败')
|
||||
}
|
||||
},
|
||||
async loadContractData() {
|
||||
try {
|
||||
const id = this.$route.params.id
|
||||
const contract = await contractApi.getById(id)
|
||||
if (contract) {
|
||||
this.contractForm = contract
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载合同数据失败')
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.contractForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await contractApi.update(this.contractForm.id, this.contractForm)
|
||||
this.$message.success('编辑成功')
|
||||
this.$router.push('/contract/list')
|
||||
} catch (error) {
|
||||
this.$message.error('编辑失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.loadContractData()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/contract/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.contract-edit {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div class="contract-list">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>合同档案</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="有效" value="active"></el-option>
|
||||
<el-option label="过期" value="expired"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="filteredContracts" style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||||
<el-table-column prop="regionName" label="区域"></el-table-column>
|
||||
<el-table-column prop="apartmentName" label="公寓"></el-table-column>
|
||||
<el-table-column prop="roomNumber" label="房间号"></el-table-column>
|
||||
<el-table-column prop="tenantName" label="租客姓名"></el-table-column>
|
||||
<el-table-column prop="startDate" label="开始日期" width="150"></el-table-column>
|
||||
<el-table-column prop="endDate" label="结束日期" width="150"></el-table-column>
|
||||
<el-table-column prop="rent" label="租金(元/月)" width="120"></el-table-column>
|
||||
<el-table-column prop="deposit" label="押金(元)" width="120"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'">{{ scope.row.status === 'active' ? '有效' : '过期' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="pagination" style="margin-top: 20px;">
|
||||
<el-pagination
|
||||
layout="prev, pager, next"
|
||||
:total="filteredContracts.length"
|
||||
:page-size="10"
|
||||
:current-page="currentPage"
|
||||
@current-change="handleCurrentChange"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { contractApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'ContractList',
|
||||
data() {
|
||||
return {
|
||||
contracts: [],
|
||||
searchForm: {
|
||||
status: ''
|
||||
},
|
||||
currentPage: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredContracts() {
|
||||
return this.contracts.filter(contract => {
|
||||
return !this.searchForm.status || contract.status == this.searchForm.status
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadContracts()
|
||||
},
|
||||
methods: {
|
||||
async loadContracts() {
|
||||
try {
|
||||
// 加载合同数据
|
||||
const contractsResponse = await contractApi.getAll()
|
||||
|
||||
// 处理合同数据,使用后端返回的关联信息
|
||||
this.contracts = contractsResponse.map(contract => {
|
||||
return {
|
||||
...contract,
|
||||
roomNumber: contract.Room ? contract.Room.roomNumber : '',
|
||||
tenantName: contract.Tenant ? contract.Tenant.name : '',
|
||||
apartmentName: contract.Room && contract.Room.Apartment ? contract.Room.Apartment.name : '',
|
||||
regionName: contract.Room && contract.Room.Apartment && contract.Room.Apartment.Region ? contract.Room.Apartment.Region.name : ''
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
this.$message.error('加载数据失败')
|
||||
}
|
||||
},
|
||||
handleAdd() {
|
||||
this.$router.push('/contract/add')
|
||||
},
|
||||
handleEdit(id) {
|
||||
this.$router.push(`/contract/edit/${id}`)
|
||||
},
|
||||
async handleDelete(id) {
|
||||
this.$confirm('确定要删除这个合同吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
await contractApi.delete(id)
|
||||
this.$message.success('删除成功')
|
||||
this.loadContracts()
|
||||
} catch (error) {
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消删除
|
||||
})
|
||||
},
|
||||
handleSearch() {
|
||||
// 搜索逻辑
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm = {
|
||||
status: ''
|
||||
}
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.contract-list {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
<template>
|
||||
<div class="house-add">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>添加房源</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="houseForm" :rules="rules" ref="houseForm" label-width="100px">
|
||||
<el-form-item label="区域" prop="regionId">
|
||||
<el-select v-model="houseForm.regionId" placeholder="请选择区域">
|
||||
<el-option v-for="region in regions" :key="region.id" :label="region.name" :value="region.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="houseForm.address" placeholder="请输入地址"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="面积(㎡)" prop="area">
|
||||
<el-input type="number" v-model="houseForm.area" placeholder="请输入面积"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="租金(元/月)" prop="price">
|
||||
<el-input type="number" v-model="houseForm.price" placeholder="请输入租金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="houseForm.status" placeholder="请选择状态">
|
||||
<el-option label="可租" value="available"></el-option>
|
||||
<el-option label="已租" value="rented"></el-option>
|
||||
<el-option label="维护中" value="maintenance"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { regionApi, houseApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'HouseAdd',
|
||||
data() {
|
||||
return {
|
||||
houseForm: {
|
||||
regionId: '',
|
||||
address: '',
|
||||
area: '',
|
||||
price: '',
|
||||
status: 'available'
|
||||
},
|
||||
regions: [],
|
||||
rules: {
|
||||
regionId: [
|
||||
{ required: true, message: '请选择区域', trigger: 'blur' }
|
||||
],
|
||||
address: [
|
||||
{ required: true, message: '请输入地址', trigger: 'blur' },
|
||||
{ min: 5, max: 100, message: '长度在 5 到 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
area: [
|
||||
{ required: true, message: '请输入面积', trigger: 'blur' },
|
||||
{ type: 'number', message: '请输入数字', trigger: 'blur' }
|
||||
],
|
||||
price: [
|
||||
{ required: true, message: '请输入租金', trigger: 'blur' },
|
||||
{ type: 'number', message: '请输入数字', trigger: 'blur' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadRegions()
|
||||
},
|
||||
methods: {
|
||||
async loadRegions() {
|
||||
try {
|
||||
const response = await regionApi.getAll()
|
||||
this.regions = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载区域数据失败')
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.houseForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await houseApi.create(this.houseForm)
|
||||
this.$message.success('添加成功')
|
||||
this.$router.push('/house/list')
|
||||
} catch (error) {
|
||||
this.$message.error('添加失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.$refs.houseForm.resetFields()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/house/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.house-add {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<div class="house-edit">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>编辑房源</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="houseForm" :rules="rules" ref="houseForm" label-width="100px">
|
||||
<el-form-item label="区域" prop="regionId">
|
||||
<el-select v-model="houseForm.regionId" placeholder="请选择区域">
|
||||
<el-option v-for="region in regions" :key="region.id" :label="region.name" :value="region.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="houseForm.address" placeholder="请输入地址"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="面积(㎡)" prop="area">
|
||||
<el-input type="number" v-model="houseForm.area" placeholder="请输入面积"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="租金(元/月)" prop="price">
|
||||
<el-input type="number" v-model="houseForm.price" placeholder="请输入租金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="houseForm.status" placeholder="请选择状态">
|
||||
<el-option label="可租" value="available"></el-option>
|
||||
<el-option label="已租" value="rented"></el-option>
|
||||
<el-option label="维护中" value="maintenance"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { regionApi, houseApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'HouseEdit',
|
||||
data() {
|
||||
return {
|
||||
houseForm: {
|
||||
id: '',
|
||||
regionId: '',
|
||||
address: '',
|
||||
area: '',
|
||||
price: '',
|
||||
status: ''
|
||||
},
|
||||
regions: [],
|
||||
rules: {
|
||||
regionId: [
|
||||
{ required: true, message: '请选择区域', trigger: 'blur' }
|
||||
],
|
||||
address: [
|
||||
{ required: true, message: '请输入地址', trigger: 'blur' },
|
||||
{ min: 5, max: 100, message: '长度在 5 到 100 个字符', trigger: 'blur' }
|
||||
],
|
||||
area: [
|
||||
{ required: true, message: '请输入面积', trigger: 'blur' },
|
||||
{ type: 'number', message: '请输入数字', trigger: 'blur' }
|
||||
],
|
||||
price: [
|
||||
{ required: true, message: '请输入租金', trigger: 'blur' },
|
||||
{ type: 'number', message: '请输入数字', trigger: 'blur' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadRegions()
|
||||
this.loadHouseData()
|
||||
},
|
||||
methods: {
|
||||
async loadRegions() {
|
||||
try {
|
||||
const response = await regionApi.getAll()
|
||||
this.regions = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载区域数据失败')
|
||||
}
|
||||
},
|
||||
async loadHouseData() {
|
||||
try {
|
||||
const id = this.$route.params.id
|
||||
const house = await houseApi.getById(id)
|
||||
if (house) {
|
||||
this.houseForm = house
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载房源数据失败')
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.houseForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await houseApi.update(this.houseForm.id, this.houseForm)
|
||||
this.$message.success('编辑成功')
|
||||
this.$router.push('/house/list')
|
||||
} catch (error) {
|
||||
this.$message.error('编辑失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.loadHouseData()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/house/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.house-edit {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
<template>
|
||||
<div class="house-list">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>房源列表</span>
|
||||
<el-button type="primary" @click="handleAdd">添加房源</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="区域">
|
||||
<el-select v-model="searchForm.regionId" placeholder="请选择区域">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option v-for="region in regions" :key="region.id" :label="region.name" :value="region.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="可租" value="available"></el-option>
|
||||
<el-option label="已租" value="rented"></el-option>
|
||||
<el-option label="维护中" value="maintenance"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="filteredHouses" style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||||
<el-table-column prop="regionName" label="区域"></el-table-column>
|
||||
<el-table-column prop="address" label="地址"></el-table-column>
|
||||
<el-table-column prop="area" label="面积(㎡)" width="100"></el-table-column>
|
||||
<el-table-column prop="price" label="租金(元/月)" width="120"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">{{ getStatusText(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="small" type="primary" @click="handleEdit(scope.row.id)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination" style="margin-top: 20px;">
|
||||
<el-pagination
|
||||
layout="prev, pager, next"
|
||||
:total="filteredHouses.length"
|
||||
:page-size="10"
|
||||
:current-page="currentPage"
|
||||
@current-change="handleCurrentChange"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { regionApi, houseApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'HouseList',
|
||||
data() {
|
||||
return {
|
||||
regions: [],
|
||||
houses: [],
|
||||
searchForm: {
|
||||
regionId: '',
|
||||
status: ''
|
||||
},
|
||||
currentPage: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredHouses() {
|
||||
return this.houses.filter(house => {
|
||||
const regionMatch = !this.searchForm.regionId || house.regionId == this.searchForm.regionId
|
||||
const statusMatch = !this.searchForm.status || house.status == this.searchForm.status
|
||||
return regionMatch && statusMatch
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
try {
|
||||
// 加载区域数据
|
||||
const regionsResponse = await regionApi.getAll()
|
||||
this.regions = regionsResponse
|
||||
|
||||
// 加载房源数据
|
||||
const housesResponse = await houseApi.getAll()
|
||||
this.houses = housesResponse.map(house => {
|
||||
const region = regionsResponse.find(r => r.id == house.regionId)
|
||||
return {
|
||||
...house,
|
||||
regionName: region ? region.name : ''
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
this.$message.error('加载数据失败')
|
||||
}
|
||||
},
|
||||
getStatusType(status) {
|
||||
switch (status) {
|
||||
case 'available': return 'success'
|
||||
case 'rented': return 'warning'
|
||||
case 'maintenance': return 'danger'
|
||||
default: return ''
|
||||
}
|
||||
},
|
||||
getStatusText(status) {
|
||||
switch (status) {
|
||||
case 'available': return '可租'
|
||||
case 'rented': return '已租'
|
||||
case 'maintenance': return '维护中'
|
||||
default: return status
|
||||
}
|
||||
},
|
||||
handleAdd() {
|
||||
this.$router.push('/house/add')
|
||||
},
|
||||
handleEdit(id) {
|
||||
this.$router.push(`/house/edit/${id}`)
|
||||
},
|
||||
async handleDelete(id) {
|
||||
this.$confirm('确定要删除这个房源吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
await houseApi.delete(id)
|
||||
this.$message.success('删除成功')
|
||||
this.loadData()
|
||||
} catch (error) {
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消删除
|
||||
})
|
||||
},
|
||||
handleSearch() {
|
||||
// 搜索逻辑
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm = {
|
||||
regionId: '',
|
||||
status: ''
|
||||
}
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.house-list {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<div class="region-add">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>添加区域</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="regionForm" :rules="rules" ref="regionForm" label-width="100px">
|
||||
<el-form-item label="区域名称" prop="name">
|
||||
<el-input v-model="regionForm.name" placeholder="请输入区域名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="区域描述" prop="description">
|
||||
<el-input type="textarea" v-model="regionForm.description" placeholder="请输入区域描述"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { regionApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RegionAdd',
|
||||
data() {
|
||||
return {
|
||||
regionForm: {
|
||||
name: '',
|
||||
description: ''
|
||||
},
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: '请输入区域名称', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
description: [
|
||||
{ required: true, message: '请输入区域描述', trigger: 'blur' },
|
||||
{ min: 5, max: 100, message: '长度在 5 到 100 个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async submitForm() {
|
||||
this.$refs.regionForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await regionApi.create(this.regionForm)
|
||||
this.$message.success('添加成功')
|
||||
this.$router.push('/region/list')
|
||||
} catch (error) {
|
||||
this.$message.error('添加失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.$refs.regionForm.resetFields()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/region/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.region-add {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<div class="region-edit">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>编辑区域</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="regionForm" :rules="rules" ref="regionForm" label-width="100px">
|
||||
<el-form-item label="区域名称" prop="name">
|
||||
<el-input v-model="regionForm.name" placeholder="请输入区域名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="区域描述" prop="description">
|
||||
<el-input type="textarea" v-model="regionForm.description" placeholder="请输入区域描述"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { regionApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RegionEdit',
|
||||
data() {
|
||||
return {
|
||||
regionForm: {
|
||||
id: '',
|
||||
name: '',
|
||||
description: ''
|
||||
},
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: '请输入区域名称', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
|
||||
],
|
||||
description: [
|
||||
{ required: true, message: '请输入区域描述', trigger: 'blur' },
|
||||
{ min: 5, max: 100, message: '长度在 5 到 100 个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadRegionData()
|
||||
},
|
||||
methods: {
|
||||
async loadRegionData() {
|
||||
try {
|
||||
const id = this.$route.params.id
|
||||
const region = await regionApi.getById(id)
|
||||
if (region) {
|
||||
this.regionForm = region
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载区域数据失败')
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.regionForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await regionApi.update(this.regionForm.id, this.regionForm)
|
||||
this.$message.success('编辑成功')
|
||||
this.$router.push('/region/list')
|
||||
} catch (error) {
|
||||
this.$message.error('编辑失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.loadRegionData()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/region/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.region-edit {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div class="region-list">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>区域列表</span>
|
||||
<el-button type="primary" @click="handleAdd">添加区域</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-table :data="regions" style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||||
<el-table-column prop="name" label="区域名称"></el-table-column>
|
||||
<el-table-column prop="description" label="区域描述"></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="small" type="primary" @click="handleEdit(scope.row.id)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination" style="margin-top: 20px;">
|
||||
<el-pagination
|
||||
layout="prev, pager, next"
|
||||
:total="regions.length"
|
||||
:page-size="10"
|
||||
:current-page="currentPage"
|
||||
@current-change="handleCurrentChange"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { regionApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RegionList',
|
||||
data() {
|
||||
return {
|
||||
regions: [],
|
||||
currentPage: 1
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadRegions()
|
||||
},
|
||||
methods: {
|
||||
async loadRegions() {
|
||||
try {
|
||||
const response = await regionApi.getAll()
|
||||
this.regions = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载区域数据失败')
|
||||
}
|
||||
},
|
||||
handleAdd() {
|
||||
this.$router.push('/region/add')
|
||||
},
|
||||
handleEdit(id) {
|
||||
this.$router.push(`/region/edit/${id}`)
|
||||
},
|
||||
async handleDelete(id) {
|
||||
this.$confirm('确定要删除这个区域吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
await regionApi.delete(id)
|
||||
this.$message.success('删除成功')
|
||||
this.loadRegions()
|
||||
} catch (error) {
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.info('已取消删除')
|
||||
})
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.region-list {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
<template>
|
||||
<div class="rental-add">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>添加租房</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="rentalForm" :rules="rules" ref="rentalForm" label-width="120px">
|
||||
<el-form-item label="房间" prop="roomId">
|
||||
<el-select v-model="rentalForm.roomId" placeholder="请选择房间" style="width: 100%">
|
||||
<el-option
|
||||
v-for="room in rooms"
|
||||
:key="room.id"
|
||||
:label="`${getApartmentName(room.apartmentId)} - ${room.roomNumber}`"
|
||||
:value="room.id.toString()"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-divider>租客信息</el-divider>
|
||||
<el-form-item label="租客姓名" prop="tenantName">
|
||||
<el-input v-model="rentalForm.tenantName" placeholder="请输入租客姓名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="租客电话" prop="tenantPhone">
|
||||
<el-input v-model="rentalForm.tenantPhone" placeholder="请输入租客电话"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证号" prop="tenantIdCard">
|
||||
<el-input v-model="rentalForm.tenantIdCard" placeholder="请输入身份证号"></el-input>
|
||||
</el-form-item>
|
||||
<el-divider>合同信息</el-divider>
|
||||
<el-form-item label="开始日期" prop="startDate">
|
||||
<el-date-picker
|
||||
v-model="rentalForm.startDate"
|
||||
type="date"
|
||||
placeholder="选择开始日期"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期" prop="endDate">
|
||||
<el-date-picker
|
||||
v-model="rentalForm.endDate"
|
||||
type="date"
|
||||
placeholder="选择结束日期"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="租金(元/月)" prop="rent">
|
||||
<el-input v-model.number="rentalForm.rent" placeholder="请输入租金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="押金(元)" prop="deposit">
|
||||
<el-input v-model.number="rentalForm.deposit" placeholder="请输入押金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="rentalForm.status" placeholder="请选择状态" style="width: 100%">
|
||||
<el-option label="有效" value="active"></el-option>
|
||||
<el-option label="到期" value="expired"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="rentalForm.remark" type="textarea" rows="3" placeholder="请输入备注信息"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { rentalApi, roomApi, apartmentApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RentalAdd',
|
||||
data() {
|
||||
return {
|
||||
rentalForm: {
|
||||
roomId: '',
|
||||
tenantName: '',
|
||||
tenantPhone: '',
|
||||
tenantIdCard: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
rent: '',
|
||||
deposit: '',
|
||||
status: 'active',
|
||||
remark: ''
|
||||
},
|
||||
rules: {
|
||||
roomId: [{ required: true, message: '请选择房间', trigger: 'blur' }],
|
||||
tenantName: [{ required: true, message: '请输入租客姓名', trigger: 'blur' }],
|
||||
tenantPhone: [{ required: true, message: '请输入租客电话', trigger: 'blur' }],
|
||||
tenantIdCard: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
|
||||
startDate: [{ required: true, message: '请选择开始日期', trigger: 'blur' }],
|
||||
endDate: [{ required: true, message: '请选择结束日期', trigger: 'blur' }],
|
||||
rent: [{ required: true, message: '请输入租金', trigger: 'blur' }],
|
||||
deposit: [{ required: true, message: '请输入押金', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'blur' }]
|
||||
},
|
||||
rooms: [],
|
||||
apartments: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
try {
|
||||
// 并行加载房间和公寓数据
|
||||
const [roomsResponse, apartmentsResponse] = await Promise.all([
|
||||
roomApi.getAll(),
|
||||
apartmentApi.getAll()
|
||||
])
|
||||
|
||||
this.rooms = roomsResponse.data || roomsResponse
|
||||
this.apartments = apartmentsResponse.data || apartmentsResponse
|
||||
|
||||
// 检查URL参数中是否有roomId
|
||||
const roomId = this.$route.query.roomId
|
||||
if (roomId) {
|
||||
// 确保roomId是字符串类型,与option的value类型一致
|
||||
this.rentalForm.roomId = roomId.toString()
|
||||
|
||||
// 查找对应的房间信息
|
||||
const room = this.rooms.find(r => r.id.toString() == roomId.toString())
|
||||
if (room) {
|
||||
// 自动填充租金
|
||||
this.rentalForm.rent = room.price
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载数据失败')
|
||||
}
|
||||
},
|
||||
getApartmentName(apartmentId) {
|
||||
if (!Array.isArray(this.apartments)) {
|
||||
return ''
|
||||
}
|
||||
const apartment = this.apartments.find(a => a.id == apartmentId)
|
||||
return apartment ? apartment.name : ''
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.rentalForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await rentalApi.create(this.rentalForm)
|
||||
this.$message.success('添加成功')
|
||||
this.$router.push('/rental/list')
|
||||
} catch (error) {
|
||||
this.$message.error('添加失败')
|
||||
}
|
||||
} else {
|
||||
this.$message.error('请填写所有必填项')
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.$refs.rentalForm.resetFields()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/rental/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rental-add {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,608 @@
|
|||
<template>
|
||||
<div class="rental-detail">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>房屋详情</span>
|
||||
<div class="action-buttons">
|
||||
<el-button v-if="room.status === 'empty'" type="primary" @click="handleRent">租房</el-button>
|
||||
<el-button v-if="room.status === 'rented'" type="warning" @click="handleCheckout">退房</el-button>
|
||||
<el-button v-if="room.status === 'empty'" type="info" @click="handleCleaning">打扫</el-button>
|
||||
<el-button v-if="room.status === 'empty'" type="danger" @click="handleMaintenance">维修</el-button>
|
||||
<el-button v-if="room.status === 'cleaning' || room.status === 'maintenance'" type="success" @click="handleComplete">完成</el-button>
|
||||
<el-button type="primary" @click="goBack">返回</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading="isLoading" class="room-info-section">
|
||||
<h2>{{ room.apartmentName }} - {{ room.roomNumber }}</h2>
|
||||
<div class="room-basic-info">
|
||||
<div class="info-item">
|
||||
<span class="label">面积:</span>
|
||||
<span class="value">{{ room.area }}㎡</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">租金:</span>
|
||||
<span class="value">¥{{ room.price }}/月</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">状态:</span>
|
||||
<span class="value">
|
||||
<el-tag :type="getStatusType(room.status)">{{ getStatusText(room.status) }}</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">创建时间:</span>
|
||||
<span class="value">{{ room.createTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="租赁档案" name="rental">
|
||||
<el-table :data="rentalHistory" style="width: 100%">
|
||||
<el-table-column prop="tenantName" label="租客" width="120"></el-table-column>
|
||||
<el-table-column prop="startDate" label="开始日期" width="150"></el-table-column>
|
||||
<el-table-column prop="endDate" label="结束日期" width="150"></el-table-column>
|
||||
<el-table-column prop="rent" label="租金(元/月)" width="120"></el-table-column>
|
||||
<el-table-column prop="deposit" label="押金(元)" width="120"></el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="150"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'">
|
||||
{{ scope.row.status === 'active' ? '在租' : '已到期' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="水费记录" name="water">
|
||||
<div class="section-header">
|
||||
<el-button type="primary" size="small" @click="handleAddWaterBill">添加水费</el-button>
|
||||
</div>
|
||||
<el-table :data="waterBills" style="width: 100%">
|
||||
<el-table-column prop="startDate" label="开始日期" width="150"></el-table-column>
|
||||
<el-table-column prop="endDate" label="结束日期" width="150"></el-table-column>
|
||||
<el-table-column prop="startReading" label="起始度数" width="120"></el-table-column>
|
||||
<el-table-column prop="endReading" label="结束度数" width="120"></el-table-column>
|
||||
<el-table-column prop="usage" label="用水量(吨)" width="120"></el-table-column>
|
||||
<el-table-column prop="unitPrice" label="单价(元/吨)" width="120"></el-table-column>
|
||||
<el-table-column prop="amount" label="费用(元)" width="100"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === 'paid' ? 'success' : 'warning'">
|
||||
{{ scope.row.status === 'paid' ? '已支付' : '未支付' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="primary" size="small" @click="handleEditWaterBill(scope.row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="handleDeleteWaterBill(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="电费记录" name="electricity">
|
||||
<div class="section-header">
|
||||
<el-button type="primary" size="small" @click="handleAddElectricityBill">添加电费</el-button>
|
||||
</div>
|
||||
<el-table :data="electricityBills" style="width: 100%">
|
||||
<el-table-column prop="startDate" label="开始日期" width="150"></el-table-column>
|
||||
<el-table-column prop="endDate" label="结束日期" width="150"></el-table-column>
|
||||
<el-table-column prop="startReading" label="起始度数" width="120"></el-table-column>
|
||||
<el-table-column prop="endReading" label="结束度数" width="120"></el-table-column>
|
||||
<el-table-column prop="usage" label="用电量(度)" width="120"></el-table-column>
|
||||
<el-table-column prop="unitPrice" label="单价(元/度)" width="120"></el-table-column>
|
||||
<el-table-column prop="amount" label="费用(元)" width="100"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === 'paid' ? 'success' : 'warning'">
|
||||
{{ scope.row.status === 'paid' ? '已支付' : '未支付' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="primary" size="small" @click="handleEditElectricityBill(scope.row)">编辑</el-button>
|
||||
<el-button type="danger" size="small" @click="handleDeleteElectricityBill(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 水费编辑对话框 -->
|
||||
<el-dialog title="编辑水费" :visible.sync="waterBillDialogVisible" width="500px">
|
||||
<el-form :model="waterBillForm" :rules="waterBillRules" ref="waterBillForm">
|
||||
<el-form-item label="开始日期" prop="startDate">
|
||||
<el-date-picker v-model="waterBillForm.startDate" type="date" placeholder="选择开始日期" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期" prop="endDate">
|
||||
<el-date-picker v-model="waterBillForm.endDate" type="date" placeholder="选择结束日期" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="起始度数" prop="startReading">
|
||||
<el-input v-model.number="waterBillForm.startReading" placeholder="请输入起始度数"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束度数" prop="endReading">
|
||||
<el-input v-model.number="waterBillForm.endReading" placeholder="请输入结束度数"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="单价(元/吨)" prop="unitPrice">
|
||||
<el-input v-model.number="waterBillForm.unitPrice" placeholder="请输入单价"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="waterBillForm.status" placeholder="请选择状态">
|
||||
<el-option label="未支付" value="unpaid"></el-option>
|
||||
<el-option label="已支付" value="paid"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="waterBillDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSaveWaterBill">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 电费编辑对话框 -->
|
||||
<el-dialog title="编辑电费" :visible.sync="electricityBillDialogVisible" width="500px">
|
||||
<el-form :model="electricityBillForm" :rules="electricityBillRules" ref="electricityBillForm">
|
||||
<el-form-item label="开始日期" prop="startDate">
|
||||
<el-date-picker v-model="electricityBillForm.startDate" type="date" placeholder="选择开始日期" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期" prop="endDate">
|
||||
<el-date-picker v-model="electricityBillForm.endDate" type="date" placeholder="选择结束日期" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="起始度数" prop="startReading">
|
||||
<el-input v-model.number="electricityBillForm.startReading" placeholder="请输入起始度数"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束度数" prop="endReading">
|
||||
<el-input v-model.number="electricityBillForm.endReading" placeholder="请输入结束度数"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="单价(元/度)" prop="unitPrice">
|
||||
<el-input v-model.number="electricityBillForm.unitPrice" placeholder="请输入单价"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="electricityBillForm.status" placeholder="请选择状态">
|
||||
<el-option label="未支付" value="unpaid"></el-option>
|
||||
<el-option label="已支付" value="paid"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="electricityBillDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSaveElectricityBill">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { roomApi, rentalApi, apartmentApi, waterBillApi, electricityBillApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RentalDetail',
|
||||
data() {
|
||||
return {
|
||||
room: {},
|
||||
rentals: [],
|
||||
apartments: [],
|
||||
rentalHistory: [],
|
||||
waterBills: [],
|
||||
electricityBills: [],
|
||||
isLoading: false,
|
||||
activeTab: 'rental',
|
||||
waterBillDialogVisible: false,
|
||||
electricityBillDialogVisible: false,
|
||||
waterBillForm: {
|
||||
id: '',
|
||||
roomId: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
startReading: '',
|
||||
endReading: '',
|
||||
unitPrice: '',
|
||||
status: 'unpaid'
|
||||
},
|
||||
electricityBillForm: {
|
||||
id: '',
|
||||
roomId: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
startReading: '',
|
||||
endReading: '',
|
||||
unitPrice: '',
|
||||
status: 'unpaid'
|
||||
},
|
||||
waterBillRules: {
|
||||
startDate: [{ required: true, message: '请选择开始日期', trigger: 'change' }],
|
||||
endDate: [{ required: true, message: '请选择结束日期', trigger: 'change' }],
|
||||
startReading: [{ required: true, message: '请输入起始度数', trigger: 'blur' }],
|
||||
endReading: [{ required: true, message: '请输入结束度数', trigger: 'blur' }],
|
||||
unitPrice: [{ required: true, message: '请输入单价', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
},
|
||||
electricityBillRules: {
|
||||
startDate: [{ required: true, message: '请选择开始日期', trigger: 'change' }],
|
||||
endDate: [{ required: true, message: '请选择结束日期', trigger: 'change' }],
|
||||
startReading: [{ required: true, message: '请输入起始度数', trigger: 'blur' }],
|
||||
endReading: [{ required: true, message: '请输入结束度数', trigger: 'blur' }],
|
||||
unitPrice: [{ required: true, message: '请输入单价', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
this.isLoading = true
|
||||
try {
|
||||
const roomId = this.$route.params.id
|
||||
|
||||
// 加载房间数据
|
||||
const roomResponse = await roomApi.getById(roomId)
|
||||
this.room = roomResponse
|
||||
|
||||
// 加载公寓数据
|
||||
const apartmentsResponse = await apartmentApi.getAll()
|
||||
this.apartments = apartmentsResponse.data || apartmentsResponse
|
||||
|
||||
// 添加公寓名称
|
||||
if (this.room.apartmentId) {
|
||||
const apartment = this.apartments.find(a => a.id == this.room.apartmentId)
|
||||
this.room.apartmentName = apartment ? apartment.name : ''
|
||||
}
|
||||
|
||||
// 加载租房数据
|
||||
const rentalsResponse = await rentalApi.getAll()
|
||||
this.rentals = rentalsResponse.data || rentalsResponse
|
||||
|
||||
// 加载水费数据
|
||||
const waterBillsResponse = await waterBillApi.getAll({ roomId })
|
||||
this.waterBills = waterBillsResponse.data || waterBillsResponse
|
||||
|
||||
// 加载电费数据
|
||||
const electricityBillsResponse = await electricityBillApi.getAll({ roomId })
|
||||
this.electricityBills = electricityBillsResponse.data || electricityBillsResponse
|
||||
|
||||
// 加载租赁历史
|
||||
this.loadRentalHistory()
|
||||
} catch (error) {
|
||||
this.$message.error('加载数据失败')
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
loadRentalHistory() {
|
||||
const roomId = this.$route.params.id
|
||||
this.rentalHistory = this.rentals
|
||||
.filter(r => r.roomId == roomId)
|
||||
.map(rental => {
|
||||
return {
|
||||
...rental,
|
||||
tenantName: rental.Tenant ? rental.Tenant.name : ''
|
||||
}
|
||||
})
|
||||
},
|
||||
getStatusType(status) {
|
||||
switch (status) {
|
||||
case 'empty': return ''
|
||||
case 'rented': return 'success'
|
||||
case 'soon_expire': return 'warning'
|
||||
case 'expired': return 'danger'
|
||||
case 'cleaning': return 'info'
|
||||
case 'maintenance': return 'danger'
|
||||
default: return ''
|
||||
}
|
||||
},
|
||||
getStatusText(status) {
|
||||
switch (status) {
|
||||
case 'empty': return '空房'
|
||||
case 'rented': return '在租'
|
||||
case 'soon_expire': return '即将到期'
|
||||
case 'expired': return '到期'
|
||||
case 'cleaning': return '打扫中'
|
||||
case 'maintenance': return '维修中'
|
||||
default: return status
|
||||
}
|
||||
},
|
||||
handleRent() {
|
||||
this.$router.push(`/rental/add?roomId=${this.room.id}`)
|
||||
},
|
||||
async handleCheckout() {
|
||||
this.$confirm('确定要退房吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
// 找到当前房间的租房记录
|
||||
const roomId = this.$route.params.id
|
||||
const rental = this.rentals.find(r => r.roomId == roomId && r.status === 'active')
|
||||
|
||||
if (rental) {
|
||||
// 更新租房记录状态
|
||||
await rentalApi.update(rental.id, { status: 'expired' })
|
||||
|
||||
// 更新房间状态
|
||||
await roomApi.update(roomId, { status: 'empty' })
|
||||
|
||||
this.$message.success('退房成功')
|
||||
this.loadData()
|
||||
} else {
|
||||
this.$message.error('未找到活跃的租房记录')
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('退房失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消退房
|
||||
})
|
||||
},
|
||||
async handleCleaning() {
|
||||
this.$confirm('确定要标记为打扫中吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const roomId = this.$route.params.id
|
||||
await roomApi.update(roomId, { status: 'cleaning' })
|
||||
this.$message.success('标记为打扫中成功')
|
||||
this.loadData()
|
||||
} catch (error) {
|
||||
this.$message.error('操作失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
},
|
||||
async handleMaintenance() {
|
||||
this.$confirm('确定要标记为维修中吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'danger'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const roomId = this.$route.params.id
|
||||
await roomApi.update(roomId, { status: 'maintenance' })
|
||||
this.$message.success('标记为维修中成功')
|
||||
this.loadData()
|
||||
} catch (error) {
|
||||
this.$message.error('操作失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
},
|
||||
async handleComplete() {
|
||||
this.$confirm('确定要完成打扫/维修并将状态改为空房吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'success'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const roomId = this.$route.params.id
|
||||
await roomApi.update(roomId, { status: 'empty' })
|
||||
this.$message.success('操作成功,房间状态已改为空房')
|
||||
this.loadData()
|
||||
} catch (error) {
|
||||
this.$message.error('操作失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
},
|
||||
handleAddWaterBill() {
|
||||
this.waterBillForm = {
|
||||
id: '',
|
||||
roomId: this.$route.params.id,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
startReading: '',
|
||||
endReading: '',
|
||||
unitPrice: '',
|
||||
status: 'unpaid'
|
||||
}
|
||||
this.waterBillDialogVisible = true
|
||||
},
|
||||
handleEditWaterBill(bill) {
|
||||
this.waterBillForm = {
|
||||
...bill,
|
||||
startDate: bill.startDate ? new Date(bill.startDate) : '',
|
||||
endDate: bill.endDate ? new Date(bill.endDate) : ''
|
||||
}
|
||||
this.waterBillDialogVisible = true
|
||||
},
|
||||
async handleSaveWaterBill() {
|
||||
try {
|
||||
const roomId = this.$route.params.id
|
||||
if (this.waterBillForm.id) {
|
||||
// 更新水费记录
|
||||
await waterBillApi.update(this.waterBillForm.id, this.waterBillForm)
|
||||
this.$message.success('水费记录更新成功')
|
||||
} else {
|
||||
// 创建水费记录
|
||||
await waterBillApi.create({
|
||||
...this.waterBillForm,
|
||||
roomId
|
||||
})
|
||||
this.$message.success('水费记录添加成功')
|
||||
}
|
||||
this.waterBillDialogVisible = false
|
||||
this.loadData()
|
||||
} catch (error) {
|
||||
this.$message.error('操作失败')
|
||||
}
|
||||
},
|
||||
async handleDeleteWaterBill(id) {
|
||||
this.$confirm('确定要删除这条水费记录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'danger'
|
||||
}).then(async () => {
|
||||
try {
|
||||
await waterBillApi.delete(id)
|
||||
this.$message.success('水费记录删除成功')
|
||||
this.loadData()
|
||||
} catch (error) {
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消删除
|
||||
})
|
||||
},
|
||||
handleAddElectricityBill() {
|
||||
this.electricityBillForm = {
|
||||
id: '',
|
||||
roomId: this.$route.params.id,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
startReading: '',
|
||||
endReading: '',
|
||||
unitPrice: '',
|
||||
status: 'unpaid'
|
||||
}
|
||||
this.electricityBillDialogVisible = true
|
||||
},
|
||||
handleEditElectricityBill(bill) {
|
||||
this.electricityBillForm = {
|
||||
...bill,
|
||||
startDate: bill.startDate ? new Date(bill.startDate) : '',
|
||||
endDate: bill.endDate ? new Date(bill.endDate) : ''
|
||||
}
|
||||
this.electricityBillDialogVisible = true
|
||||
},
|
||||
async handleSaveElectricityBill() {
|
||||
try {
|
||||
const roomId = this.$route.params.id
|
||||
if (this.electricityBillForm.id) {
|
||||
// 更新电费记录
|
||||
await electricityBillApi.update(this.electricityBillForm.id, this.electricityBillForm)
|
||||
this.$message.success('电费记录更新成功')
|
||||
} else {
|
||||
// 创建电费记录
|
||||
await electricityBillApi.create({
|
||||
...this.electricityBillForm,
|
||||
roomId
|
||||
})
|
||||
this.$message.success('电费记录添加成功')
|
||||
}
|
||||
this.electricityBillDialogVisible = false
|
||||
this.loadData()
|
||||
} catch (error) {
|
||||
this.$message.error('操作失败')
|
||||
}
|
||||
},
|
||||
async handleDeleteElectricityBill(id) {
|
||||
this.$confirm('确定要删除这条电费记录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'danger'
|
||||
}).then(async () => {
|
||||
try {
|
||||
await electricityBillApi.delete(id)
|
||||
this.$message.success('电费记录删除成功')
|
||||
this.loadData()
|
||||
} catch (error) {
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消删除
|
||||
})
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/rental/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rental-detail {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.room-info-section {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.room-info-section h2 {
|
||||
margin-bottom: 20px;
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.room-basic-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-item .label {
|
||||
width: 80px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.info-item .value {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.rental-history-section {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.rental-history-section h3 {
|
||||
margin-bottom: 15px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.water-bill-section {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.electricity-bill-section {
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<div class="rental-edit">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>编辑租房</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="rentalForm" :rules="rules" ref="rentalForm" label-width="120px">
|
||||
<el-form-item label="房间" prop="roomId">
|
||||
<el-select v-model="rentalForm.roomId" placeholder="请选择房间" style="width: 100%">
|
||||
<el-option
|
||||
v-for="room in rooms"
|
||||
:key="room.id"
|
||||
:label="`${getApartmentName(room.apartmentId)} - ${room.roomNumber}`"
|
||||
:value="room.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-divider>租客信息</el-divider>
|
||||
<el-form-item label="租客姓名" prop="tenantName">
|
||||
<el-input v-model="rentalForm.tenantName" placeholder="请输入租客姓名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="租客电话" prop="tenantPhone">
|
||||
<el-input v-model="rentalForm.tenantPhone" placeholder="请输入租客电话"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证号" prop="tenantIdCard">
|
||||
<el-input v-model="rentalForm.tenantIdCard" placeholder="请输入身份证号"></el-input>
|
||||
</el-form-item>
|
||||
<el-divider>合同信息</el-divider>
|
||||
<el-form-item label="开始日期" prop="startDate">
|
||||
<el-date-picker
|
||||
v-model="rentalForm.startDate"
|
||||
type="date"
|
||||
placeholder="选择开始日期"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束日期" prop="endDate">
|
||||
<el-date-picker
|
||||
v-model="rentalForm.endDate"
|
||||
type="date"
|
||||
placeholder="选择结束日期"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="租金(元/月)" prop="rent">
|
||||
<el-input v-model.number="rentalForm.rent" placeholder="请输入租金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="押金(元)" prop="deposit">
|
||||
<el-input v-model.number="rentalForm.deposit" placeholder="请输入押金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="rentalForm.status" placeholder="请选择状态" style="width: 100%">
|
||||
<el-option label="有效" value="active"></el-option>
|
||||
<el-option label="到期" value="expired"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { rentalApi, roomApi, apartmentApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RentalEdit',
|
||||
data() {
|
||||
return {
|
||||
rentalForm: {
|
||||
id: '',
|
||||
roomId: '',
|
||||
tenantName: '',
|
||||
tenantPhone: '',
|
||||
tenantIdCard: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
rent: '',
|
||||
deposit: '',
|
||||
status: ''
|
||||
},
|
||||
rules: {
|
||||
roomId: [{ required: true, message: '请选择房间', trigger: 'blur' }],
|
||||
tenantName: [{ required: true, message: '请输入租客姓名', trigger: 'blur' }],
|
||||
tenantPhone: [{ required: true, message: '请输入租客电话', trigger: 'blur' }],
|
||||
tenantIdCard: [{ required: true, message: '请输入身份证号', trigger: 'blur' }],
|
||||
startDate: [{ required: true, message: '请选择开始日期', trigger: 'blur' }],
|
||||
endDate: [{ required: true, message: '请选择结束日期', trigger: 'blur' }],
|
||||
rent: [{ required: true, message: '请输入租金', trigger: 'blur' }],
|
||||
deposit: [{ required: true, message: '请输入押金', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'blur' }]
|
||||
},
|
||||
rooms: [],
|
||||
apartments: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
this.loadRentalData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
try {
|
||||
// 并行加载房间和公寓数据
|
||||
const [roomsResponse, apartmentsResponse] = await Promise.all([
|
||||
roomApi.getAll(),
|
||||
apartmentApi.getAll()
|
||||
])
|
||||
|
||||
this.rooms = roomsResponse
|
||||
this.apartments = apartmentsResponse
|
||||
} catch (error) {
|
||||
this.$message.error('加载数据失败')
|
||||
}
|
||||
},
|
||||
async loadRentalData() {
|
||||
try {
|
||||
const id = this.$route.params.id
|
||||
const rental = await rentalApi.getById(id)
|
||||
if (rental) {
|
||||
this.rentalForm = {
|
||||
...rental,
|
||||
startDate: new Date(rental.startDate),
|
||||
endDate: new Date(rental.endDate)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载租房数据失败')
|
||||
}
|
||||
},
|
||||
getApartmentName(apartmentId) {
|
||||
const apartment = this.apartments.find(a => a.id == apartmentId)
|
||||
return apartment ? apartment.name : ''
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.rentalForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await rentalApi.update(this.rentalForm.id, this.rentalForm)
|
||||
this.$message.success('编辑成功')
|
||||
this.$router.push('/rental/list')
|
||||
} catch (error) {
|
||||
this.$message.error('编辑失败')
|
||||
}
|
||||
} else {
|
||||
this.$message.error('请填写所有必填项')
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.loadRentalData()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/rental/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rental-edit {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
<template>
|
||||
<div class="rental-list">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>租房管理</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="公寓">
|
||||
<el-select v-model="searchForm.apartmentId" placeholder="请选择公寓">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option v-for="apartment in apartments" :key="apartment.id" :label="apartment.name" :value="apartment.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="空房" value="empty"></el-option>
|
||||
<el-option label="在租" value="rented"></el-option>
|
||||
<el-option label="即将到期" value="soon_expire"></el-option>
|
||||
<el-option label="已到期" value="expired"></el-option>
|
||||
<el-option label="打扫中" value="cleaning"></el-option>
|
||||
<el-option label="维修中" value="maintenance"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="room-cards">
|
||||
<div
|
||||
v-for="room in rooms"
|
||||
:key="room.id"
|
||||
@click="handleRoomClick(room.id)"
|
||||
style="cursor: pointer;"
|
||||
>
|
||||
<el-card
|
||||
:class="['room-card', `status-${room.status}`]"
|
||||
>
|
||||
<div class="room-card-header">
|
||||
<h3>{{ getApartmentName(room.apartmentId) }}</h3>
|
||||
<el-tag :type="getStatusType(room.status)">{{ getStatusText(room.status) }}</el-tag>
|
||||
</div>
|
||||
<div class="room-card-body">
|
||||
<div class="room-info">
|
||||
<span class="room-number">{{ room.roomNumber }}</span>
|
||||
<span class="room-area">{{ room.area }}㎡</span>
|
||||
<span class="room-price">¥{{ room.price }}/月</span>
|
||||
</div>
|
||||
<div class="rental-info" v-if="room.Rentals && room.Rentals.length > 0">
|
||||
<p>租客: {{ room.Rentals[0].Tenant.name }}</p>
|
||||
<p>租期: {{ room.Rentals[0].startDate }} 至 {{ room.Rentals[0].endDate }}</p>
|
||||
</div>
|
||||
<div class="rental-info" v-else>
|
||||
<p>暂无租客信息</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination" style="margin-top: 20px;">
|
||||
<el-pagination
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page="currentPage"
|
||||
:page-sizes="[10, 20, 50, 100, 99999]"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { roomApi, apartmentApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RentalList',
|
||||
data() {
|
||||
return {
|
||||
rooms: [],
|
||||
apartments: [],
|
||||
total: 0,
|
||||
searchForm: {
|
||||
apartmentId: '',
|
||||
status: ''
|
||||
},
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
if (this.isLoading) return
|
||||
|
||||
this.isLoading = true
|
||||
try {
|
||||
// 加载公寓数据
|
||||
const apartmentsResponse = await apartmentApi.getAll()
|
||||
this.apartments = apartmentsResponse.data || apartmentsResponse
|
||||
|
||||
// 构建查询参数
|
||||
const params = {
|
||||
apartmentId: this.searchForm.apartmentId,
|
||||
status: this.searchForm.status,
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize
|
||||
}
|
||||
|
||||
// 加载房间数据(包含租房信息)
|
||||
const roomsResponse = await roomApi.getAll(params)
|
||||
if (roomsResponse.data) {
|
||||
this.rooms = roomsResponse.data
|
||||
this.total = roomsResponse.total
|
||||
} else {
|
||||
// 兼容旧的返回格式
|
||||
this.rooms = roomsResponse
|
||||
this.total = roomsResponse.length
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载数据失败')
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
getApartmentName(apartmentId) {
|
||||
const apartment = this.apartments.find(a => a.id == apartmentId)
|
||||
return apartment ? apartment.name : ''
|
||||
},
|
||||
getStatusType(status) {
|
||||
switch (status) {
|
||||
case 'empty': return ''
|
||||
case 'rented': return 'success'
|
||||
case 'soon_expire': return 'warning'
|
||||
case 'expired': return 'danger'
|
||||
case 'cleaning': return 'info'
|
||||
case 'maintenance': return 'danger'
|
||||
default: return ''
|
||||
}
|
||||
},
|
||||
getStatusText(status) {
|
||||
switch (status) {
|
||||
case 'empty': return '空房'
|
||||
case 'rented': return '在租'
|
||||
case 'soon_expire': return '即将到期'
|
||||
case 'expired': return '到期'
|
||||
case 'cleaning': return '打扫中'
|
||||
case 'maintenance': return '维修中'
|
||||
default: return status
|
||||
}
|
||||
},
|
||||
|
||||
handleAdd() {
|
||||
this.$router.push('/rental/add')
|
||||
},
|
||||
handleRoomClick(roomId) {
|
||||
try {
|
||||
this.$router.push(`/rental/detail/${roomId}`)
|
||||
} catch (error) {
|
||||
console.error('Navigation error:', error)
|
||||
}
|
||||
},
|
||||
handleSearch() {
|
||||
this.currentPage = 1
|
||||
this.loadData()
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm = {
|
||||
apartmentId: '',
|
||||
status: ''
|
||||
}
|
||||
this.currentPage = 1
|
||||
this.loadData()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.loadData()
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val
|
||||
this.currentPage = 1
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rental-list {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.room-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.room-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid #e4e7ed;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
height: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.room-card * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.room-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.room-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.room-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.room-card-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.room-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.room-number {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.room-area {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.room-price {
|
||||
color: #409EFF;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.rental-info {
|
||||
margin-top: 10px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.rental-info p {
|
||||
margin: 5px 0;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 状态样式 */
|
||||
.status-empty {
|
||||
border-color: #ffffff;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.status-rented {
|
||||
border-color: #67c23a;
|
||||
background-color: #f0f9eb;
|
||||
}
|
||||
|
||||
.status-soon_expire {
|
||||
border-color: #e6a23c;
|
||||
background-color: #fdf6ec;
|
||||
}
|
||||
|
||||
.status-expired {
|
||||
border-color: #f56c6c;
|
||||
background-color: #fef0f0;
|
||||
}
|
||||
|
||||
.status-cleaning {
|
||||
border-color: #909399;
|
||||
background-color: #f4f4f5;
|
||||
}
|
||||
|
||||
.status-maintenance {
|
||||
border-color: #303133;
|
||||
background-color: #f4f4f5;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<div class="room-add">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>添加房间</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="roomForm" :rules="rules" ref="roomForm" label-width="100px">
|
||||
<el-form-item label="公寓" prop="apartmentId">
|
||||
<el-select v-model="roomForm.apartmentId" placeholder="请选择公寓">
|
||||
<el-option v-for="apartment in apartments" :key="apartment.id" :label="apartment.name" :value="apartment.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="房间号" prop="roomNumber">
|
||||
<el-input v-model="roomForm.roomNumber" placeholder="请输入房间号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="面积(㎡)" prop="area">
|
||||
<el-input type="number" v-model="roomForm.area" placeholder="请输入面积"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="租金(元/月)" prop="price">
|
||||
<el-input type="number" v-model="roomForm.price" placeholder="请输入租金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="roomForm.status" placeholder="请选择状态">
|
||||
<el-option label="空房" value="empty"></el-option>
|
||||
<el-option label="在租" value="rented"></el-option>
|
||||
<el-option label="即将到期" value="soon_expire"></el-option>
|
||||
<el-option label="已到期" value="expired"></el-option>
|
||||
<el-option label="打扫中" value="cleaning"></el-option>
|
||||
<el-option label="维修中" value="maintenance"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { roomApi, apartmentApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RoomAdd',
|
||||
data() {
|
||||
return {
|
||||
roomForm: {
|
||||
apartmentId: '',
|
||||
roomNumber: '',
|
||||
area: '',
|
||||
price: '',
|
||||
status: 'empty'
|
||||
},
|
||||
apartments: [],
|
||||
rules: {
|
||||
apartmentId: [
|
||||
{ required: true, message: '请选择公寓', trigger: 'blur' }
|
||||
],
|
||||
roomNumber: [
|
||||
{ required: true, message: '请输入房间号', trigger: 'blur' },
|
||||
{ min: 1, max: 10, message: '长度在 1 到 10 个字符', trigger: 'blur' }
|
||||
],
|
||||
area: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
callback()
|
||||
} else if (isNaN(Number(value))) {
|
||||
callback(new Error('请输入数字'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'blur' }
|
||||
],
|
||||
price: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
callback()
|
||||
} else if (isNaN(Number(value))) {
|
||||
callback(new Error('请输入数字'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'blur' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadApartments()
|
||||
},
|
||||
methods: {
|
||||
async loadApartments() {
|
||||
try {
|
||||
const response = await apartmentApi.getAll()
|
||||
this.apartments = response.data || response
|
||||
} catch (error) {
|
||||
this.$message.error('加载公寓数据失败')
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.roomForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await roomApi.create(this.roomForm)
|
||||
this.$message.success('添加成功')
|
||||
this.$router.push('/room/list')
|
||||
} catch (error) {
|
||||
this.$message.error('添加失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.$refs.roomForm.resetFields()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/room/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.room-add {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<template>
|
||||
<div class="room-edit">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>编辑房间</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="roomForm" :rules="rules" ref="roomForm" label-width="100px">
|
||||
<el-form-item label="公寓" prop="apartmentId">
|
||||
<el-select v-model="roomForm.apartmentId" placeholder="请选择公寓">
|
||||
<el-option v-for="apartment in apartments" :key="apartment.id" :label="apartment.name" :value="apartment.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="房间号" prop="roomNumber">
|
||||
<el-input v-model="roomForm.roomNumber" placeholder="请输入房间号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="面积(㎡)" prop="area">
|
||||
<el-input type="number" v-model="roomForm.area" placeholder="请输入面积"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="租金(元/月)" prop="price">
|
||||
<el-input type="number" v-model="roomForm.price" placeholder="请输入租金"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="roomForm.status" placeholder="请选择状态">
|
||||
<el-option label="空房" value="empty"></el-option>
|
||||
<el-option label="在租" value="rented"></el-option>
|
||||
<el-option label="即将到期" value="soon_expire"></el-option>
|
||||
<el-option label="已到期" value="expired"></el-option>
|
||||
<el-option label="打扫中" value="cleaning"></el-option>
|
||||
<el-option label="维修中" value="maintenance"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { roomApi, apartmentApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RoomEdit',
|
||||
data() {
|
||||
return {
|
||||
roomForm: {
|
||||
id: '',
|
||||
apartmentId: '',
|
||||
roomNumber: '',
|
||||
area: '',
|
||||
price: '',
|
||||
status: ''
|
||||
},
|
||||
apartments: [],
|
||||
rules: {
|
||||
apartmentId: [
|
||||
{ required: true, message: '请选择公寓', trigger: 'blur' }
|
||||
],
|
||||
roomNumber: [
|
||||
{ required: true, message: '请输入房间号', trigger: 'blur' },
|
||||
{ min: 1, max: 10, message: '长度在 1 到 10 个字符', trigger: 'blur' }
|
||||
],
|
||||
area: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
callback()
|
||||
} else if (isNaN(Number(value))) {
|
||||
callback(new Error('请输入数字'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'blur' }
|
||||
],
|
||||
price: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
callback()
|
||||
} else if (isNaN(Number(value))) {
|
||||
callback(new Error('请输入数字'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'blur' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadApartments()
|
||||
this.loadRoomData()
|
||||
},
|
||||
methods: {
|
||||
async loadApartments() {
|
||||
try {
|
||||
const response = await apartmentApi.getAll()
|
||||
this.apartments = response.data || response
|
||||
} catch (error) {
|
||||
this.$message.error('加载公寓数据失败')
|
||||
}
|
||||
},
|
||||
async loadRoomData() {
|
||||
try {
|
||||
const id = this.$route.params.id
|
||||
const room = await roomApi.getById(id)
|
||||
if (room) {
|
||||
this.roomForm = room
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载房间数据失败')
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.roomForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await roomApi.update(this.roomForm.id, this.roomForm)
|
||||
this.$message.success('编辑成功')
|
||||
this.$router.push('/room/list')
|
||||
} catch (error) {
|
||||
this.$message.error('编辑失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.loadRoomData()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/room/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.room-edit {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
<template>
|
||||
<div class="room-list">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>房间列表</span>
|
||||
<el-button type="primary" @click="handleAdd">添加房间</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="公寓">
|
||||
<el-select v-model="searchForm.apartmentId" placeholder="请选择公寓">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option v-for="apartment in apartments" :key="apartment.id" :label="apartment.name" :value="apartment.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="请选择状态">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="空房" value="empty"></el-option>
|
||||
<el-option label="在租" value="rented"></el-option>
|
||||
<el-option label="即将到期" value="soon_expire"></el-option>
|
||||
<el-option label="已到期" value="expired"></el-option>
|
||||
<el-option label="打扫中" value="cleaning"></el-option>
|
||||
<el-option label="维修中" value="maintenance"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="房间号">
|
||||
<el-input v-model="searchForm.roomNumber" placeholder="请输入房间号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
:data="rooms"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column prop="apartmentName" label="公寓" width="150"></el-table-column>
|
||||
<el-table-column prop="roomNumber" label="房间号" width="100"></el-table-column>
|
||||
<el-table-column prop="area" label="面积(㎡)" width="100"></el-table-column>
|
||||
<el-table-column prop="price" label="租金(元/月)" width="120"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">{{ getStatusText(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="small" type="primary" @click="handleEdit(scope.row.id)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination" style="margin-top: 20px;">
|
||||
<el-pagination
|
||||
layout="prev, pager, next"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page="currentPage"
|
||||
@current-change="handleCurrentChange"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { roomApi, apartmentApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RoomList',
|
||||
data() {
|
||||
return {
|
||||
apartments: [],
|
||||
rooms: [],
|
||||
total: 0,
|
||||
searchForm: {
|
||||
apartmentId: '',
|
||||
status: '',
|
||||
roomNumber: ''
|
||||
},
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
if (this.isLoading) return
|
||||
|
||||
this.isLoading = true
|
||||
try {
|
||||
// 加载公寓数据
|
||||
const apartmentsResponse = await apartmentApi.getAll()
|
||||
this.apartments = apartmentsResponse.data || apartmentsResponse
|
||||
|
||||
// 构建查询参数
|
||||
const params = {
|
||||
apartmentId: this.searchForm.apartmentId,
|
||||
status: this.searchForm.status,
|
||||
roomNumber: this.searchForm.roomNumber,
|
||||
page: this.currentPage,
|
||||
pageSize: this.pageSize
|
||||
}
|
||||
|
||||
// 加载房间数据
|
||||
const roomsResponse = await roomApi.getAll(params)
|
||||
if (roomsResponse.data) {
|
||||
this.rooms = roomsResponse.data.map(room => {
|
||||
// 为房间添加公寓名称
|
||||
const apartment = this.apartments.find(a => a.id == room.apartmentId)
|
||||
return {
|
||||
...room,
|
||||
apartmentName: apartment ? apartment.name : ''
|
||||
}
|
||||
})
|
||||
this.total = roomsResponse.total
|
||||
} else {
|
||||
// 兼容旧的返回格式
|
||||
this.rooms = roomsResponse.map(room => {
|
||||
const apartment = this.apartments.find(a => a.id == room.apartmentId)
|
||||
return {
|
||||
...room,
|
||||
apartmentName: apartment ? apartment.name : ''
|
||||
}
|
||||
})
|
||||
this.total = roomsResponse.length
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载数据失败')
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
getStatusType(status) {
|
||||
switch (status) {
|
||||
case 'empty': return ''
|
||||
case 'rented': return 'success'
|
||||
case 'soon_expire': return 'warning'
|
||||
case 'expired': return 'danger'
|
||||
case 'cleaning': return 'info'
|
||||
case 'maintenance': return 'danger'
|
||||
default: return ''
|
||||
}
|
||||
},
|
||||
getStatusText(status) {
|
||||
switch (status) {
|
||||
case 'empty': return '空房'
|
||||
case 'rented': return '在租'
|
||||
case 'soon_expire': return '即将到期'
|
||||
case 'expired': return '到期'
|
||||
case 'cleaning': return '打扫中'
|
||||
case 'maintenance': return '维修中'
|
||||
default: return status
|
||||
}
|
||||
},
|
||||
handleAdd() {
|
||||
this.$router.push('/room/add')
|
||||
},
|
||||
handleEdit(id) {
|
||||
this.$router.push(`/room/edit/${id}`)
|
||||
},
|
||||
async handleDelete(id) {
|
||||
this.$confirm('确定要删除这个房间吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
await roomApi.delete(id)
|
||||
this.$message.success('删除成功')
|
||||
this.loadData()
|
||||
} catch (error) {
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消删除
|
||||
})
|
||||
},
|
||||
handleSearch() {
|
||||
this.currentPage = 1
|
||||
this.loadData()
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm = {
|
||||
apartmentId: '',
|
||||
status: '',
|
||||
roomNumber: ''
|
||||
}
|
||||
this.currentPage = 1
|
||||
this.loadData()
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.room-list {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<div class="room-statistics">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>房间状态统计</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container">
|
||||
<el-empty description="图表功能暂未实现" style="margin: 40px 0;"></el-empty>
|
||||
</div>
|
||||
<el-table :data="roomStatusData" style="width: 100%; margin-top: 20px;">
|
||||
<el-table-column prop="status" label="状态" width="120"></el-table-column>
|
||||
<el-table-column prop="count" label="数量"></el-table-column>
|
||||
<el-table-column prop="percentage" label="占比"></el-table-column>
|
||||
</el-table>
|
||||
<div class="total-count" style="margin-top: 20px; text-align: right;">
|
||||
<el-tag size="large" type="primary">房间总数:{{ totalCount }} 间</el-tag>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { statisticsApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RoomStatistics',
|
||||
data() {
|
||||
return {
|
||||
roomStatusData: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalCount() {
|
||||
return this.roomStatusData.reduce((sum, item) => sum + item.count, 0)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadRoomStatusData()
|
||||
},
|
||||
methods: {
|
||||
async loadRoomStatusData() {
|
||||
try {
|
||||
const response = await statisticsApi.getRoomStatus()
|
||||
const data = response
|
||||
const total = data.reduce((sum, item) => sum + item.count, 0)
|
||||
this.roomStatusData = data.map(item => ({
|
||||
...item,
|
||||
percentage: `${((item.count / total) * 100).toFixed(2)}%`
|
||||
}))
|
||||
} catch (error) {
|
||||
this.$message.error('加载房间状态统计数据失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.room-statistics {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div class="rent-statistics">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>租金统计</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="chart-container">
|
||||
<el-empty description="图表功能暂未实现" style="margin: 40px 0;"></el-empty>
|
||||
</div>
|
||||
<el-table :data="rentData" style="width: 100%; margin-top: 20px;">
|
||||
<el-table-column prop="month" label="月份" width="120"></el-table-column>
|
||||
<el-table-column prop="amount" label="租金收入(元)"></el-table-column>
|
||||
</el-table>
|
||||
<div class="total-amount" style="margin-top: 20px; text-align: right;">
|
||||
<el-tag size="large" type="primary">总租金收入:{{ totalAmount }} 元</el-tag>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { statisticsApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'RentStatistics',
|
||||
data() {
|
||||
return {
|
||||
rentData: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalAmount() {
|
||||
return this.rentData.reduce((sum, item) => sum + item.amount, 0)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadRentData()
|
||||
},
|
||||
methods: {
|
||||
async loadRentData() {
|
||||
try {
|
||||
const response = await statisticsApi.getRentData()
|
||||
this.rentData = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载租金统计数据失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rent-statistics {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<template>
|
||||
<div class="tenant-add">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>添加租客</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="tenantForm" :rules="rules" ref="tenantForm" label-width="100px">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="tenantForm.name" placeholder="请输入姓名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="电话" prop="phone">
|
||||
<el-input v-model="tenantForm.phone" placeholder="请输入电话"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证号" prop="idCard">
|
||||
<el-input v-model="tenantForm.idCard" placeholder="请输入身份证号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { tenantApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'TenantAdd',
|
||||
data() {
|
||||
return {
|
||||
tenantForm: {
|
||||
name: '',
|
||||
phone: '',
|
||||
idCard: ''
|
||||
},
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: '请输入姓名', trigger: 'blur' },
|
||||
{ min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '请输入电话', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的电话号码', trigger: 'blur' }
|
||||
],
|
||||
idCard: [
|
||||
{ required: true, message: '请输入身份证号', trigger: 'blur' },
|
||||
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '请输入正确的身份证号码', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async submitForm() {
|
||||
this.$refs.tenantForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await tenantApi.create(this.tenantForm)
|
||||
this.$message.success('添加成功')
|
||||
this.$router.push('/tenant/list')
|
||||
} catch (error) {
|
||||
this.$message.error('添加失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.$refs.tenantForm.resetFields()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/tenant/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tenant-add {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<div class="tenant-edit">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>编辑租客</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="tenantForm" :rules="rules" ref="tenantForm" label-width="100px">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="tenantForm.name" placeholder="请输入姓名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="电话" prop="phone">
|
||||
<el-input v-model="tenantForm.phone" placeholder="请输入电话"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份证号" prop="idCard">
|
||||
<el-input v-model="tenantForm.idCard" placeholder="请输入身份证号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm">提交</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
<el-button @click="goBack">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { tenantApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'TenantEdit',
|
||||
data() {
|
||||
return {
|
||||
tenantForm: {
|
||||
id: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
idCard: ''
|
||||
},
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: '请输入姓名', trigger: 'blur' },
|
||||
{ min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ required: true, message: '请输入电话', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的电话号码', trigger: 'blur' }
|
||||
],
|
||||
idCard: [
|
||||
{ required: true, message: '请输入身份证号', trigger: 'blur' },
|
||||
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: '请输入正确的身份证号码', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadTenantData()
|
||||
},
|
||||
methods: {
|
||||
async loadTenantData() {
|
||||
try {
|
||||
const id = this.$route.params.id
|
||||
const tenant = await tenantApi.getById(id)
|
||||
if (tenant) {
|
||||
this.tenantForm = tenant
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载租客数据失败')
|
||||
}
|
||||
},
|
||||
async submitForm() {
|
||||
this.$refs.tenantForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
await tenantApi.update(this.tenantForm.id, this.tenantForm)
|
||||
this.$message.success('编辑成功')
|
||||
this.$router.push('/tenant/list')
|
||||
} catch (error) {
|
||||
this.$message.error('编辑失败')
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm() {
|
||||
this.loadTenantData()
|
||||
},
|
||||
goBack() {
|
||||
this.$router.push('/tenant/list')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tenant-edit {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<div class="tenant-list">
|
||||
<el-card>
|
||||
<template slot="header">
|
||||
<div class="card-header">
|
||||
<span>租客档案</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :inline="true" :model="searchForm" class="search-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="searchForm.name" placeholder="请输入姓名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="电话">
|
||||
<el-input v-model="searchForm.phone" placeholder="请输入电话"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="filteredTenants" style="width: 100%">
|
||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
||||
<el-table-column prop="name" label="姓名"></el-table-column>
|
||||
<el-table-column prop="phone" label="电话"></el-table-column>
|
||||
<el-table-column prop="idCard" label="身份证号"></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="pagination" style="margin-top: 20px;">
|
||||
<el-pagination
|
||||
layout="prev, pager, next"
|
||||
:total="filteredTenants.length"
|
||||
:page-size="10"
|
||||
:current-page="currentPage"
|
||||
@current-change="handleCurrentChange"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { tenantApi } from '../../api/api'
|
||||
|
||||
export default {
|
||||
name: 'TenantList',
|
||||
data() {
|
||||
return {
|
||||
tenants: [],
|
||||
searchForm: {
|
||||
name: '',
|
||||
phone: ''
|
||||
},
|
||||
currentPage: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredTenants() {
|
||||
return this.tenants.filter(tenant => {
|
||||
const nameMatch = !this.searchForm.name || tenant.name.includes(this.searchForm.name)
|
||||
const phoneMatch = !this.searchForm.phone || tenant.phone.includes(this.searchForm.phone)
|
||||
return nameMatch && phoneMatch
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadTenants()
|
||||
},
|
||||
methods: {
|
||||
async loadTenants() {
|
||||
try {
|
||||
const response = await tenantApi.getAll()
|
||||
this.tenants = response
|
||||
} catch (error) {
|
||||
this.$message.error('加载租客数据失败')
|
||||
}
|
||||
},
|
||||
handleAdd() {
|
||||
this.$router.push('/tenant/add')
|
||||
},
|
||||
handleEdit(id) {
|
||||
this.$router.push(`/tenant/edit/${id}`)
|
||||
},
|
||||
async handleDelete(id) {
|
||||
this.$confirm('确定要删除这个租客吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
try {
|
||||
await tenantApi.delete(id)
|
||||
this.$message.success('删除成功')
|
||||
this.loadTenants()
|
||||
} catch (error) {
|
||||
this.$message.error('删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消删除
|
||||
})
|
||||
},
|
||||
handleSearch() {
|
||||
// 搜索逻辑
|
||||
},
|
||||
resetSearch() {
|
||||
this.searchForm = {
|
||||
name: '',
|
||||
phone: ''
|
||||
}
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tenant-list {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue