手机端

This commit is contained in:
wangxiaoxian 2026-03-06 00:42:43 +08:00
parent fcf97c48bd
commit de9df89004
20 changed files with 2169 additions and 546 deletions

View File

@ -1,12 +1,30 @@
<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 height="60px" class="app-header">
<div class="header-left">
<!-- 移动端菜单按钮 -->
<el-button
v-if="isMobile"
type="text"
class="menu-toggle-btn"
@click="drawerVisible = true"
>
<i class="el-icon-s-fold" style="font-size: 24px; color: white;"></i>
</el-button>
<h1 class="app-title">租房管理系统</h1>
</div>
<el-button type="primary" plain size="small" @click="logout">退出</el-button>
</el-header>
<el-container>
<el-aside width="200px" style="background-color: #f0f2f5; min-height: calc(100vh - 60px);">
<!-- PC端侧边栏 -->
<el-aside
v-if="!isMobile"
width="200px"
class="app-aside"
>
<el-menu
:default-active="activeIndex"
class="el-menu-vertical-demo"
@ -53,11 +71,74 @@
</el-menu-item>
</el-menu>
</el-aside>
<el-main style="padding: 20px;">
<!-- 主内容区 -->
<el-main class="app-main">
<router-view />
</el-main>
</el-container>
</el-container>
<!-- 移动端侧边栏抽屉 -->
<el-drawer
:visible.sync="drawerVisible"
:with-header="false"
:size="drawerWidth"
direction="ltr"
class="mobile-drawer"
>
<div class="drawer-header">
<h3>菜单</h3>
<el-button type="text" @click="drawerVisible = false">
<i class="el-icon-close" style="font-size: 20px;"></i>
</el-button>
</div>
<el-menu
:default-active="activeIndex"
class="el-menu-vertical-demo"
@select="handleMobileSelect"
background-color="#fff"
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="rental-archive">
<i class="el-icon-document"></i>
<span>租赁档案</span>
</el-menu-item>
<el-menu-item index="water-archive">
<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-drawer>
</div>
</template>
@ -66,13 +147,40 @@ export default {
name: 'App',
data() {
return {
activeIndex: 'dashboard'
activeIndex: 'dashboard',
isMobile: false,
drawerVisible: false,
drawerWidth: '70%'
}
},
mounted() {
this.checkDevice()
window.addEventListener('resize', this.checkDevice)
},
beforeDestroy() {
window.removeEventListener('resize', this.checkDevice)
},
methods: {
checkDevice() {
const width = window.innerWidth
this.isMobile = width <= 768
//
if (width <= 375) {
this.drawerWidth = '80%'
} else if (width <= 768) {
this.drawerWidth = '70%'
}
},
handleSelect(key, keyPath) {
this.activeIndex = key
//
this.navigateToRoute(key)
},
handleMobileSelect(key, keyPath) {
this.activeIndex = key
this.drawerVisible = false
this.navigateToRoute(key)
},
navigateToRoute(key) {
const routeMap = {
'dashboard': '/',
'region-list': '/region/list',
@ -93,7 +201,6 @@ export default {
}
},
logout() {
// 退
this.$message.success('退出成功')
}
}
@ -114,8 +221,179 @@ export default {
color: #2c3e50;
}
/* 顶部导航栏样式 */
.app-header {
background-color: #333;
color: white;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
}
.header-left {
display: flex;
align-items: center;
gap: 15px;
}
.menu-toggle-btn {
padding: 0;
margin: 0;
}
.app-title {
margin: 0;
font-size: 18px;
}
/* 侧边栏样式 */
.app-aside {
background-color: #f0f2f5;
min-height: calc(100vh - 60px);
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
</style>
/* 主内容区样式 */
.app-main {
padding: 20px;
background-color: #f5f7fa;
min-height: calc(100vh - 60px);
}
/* 移动端抽屉样式 */
.mobile-drawer .el-drawer__body {
padding: 0;
}
.drawer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid #ebeef5;
background-color: #f5f7fa;
}
.drawer-header h3 {
margin: 0;
font-size: 16px;
color: #333;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.app-header {
padding: 0 15px;
}
.app-title {
font-size: 16px;
}
.app-main {
padding: 10px;
}
/* 表格横向滚动 */
.el-table {
width: 100%;
overflow-x: auto;
}
.el-table__body-wrapper {
overflow-x: auto;
}
/* 表单元素适配 */
.el-form-item {
margin-bottom: 15px;
}
.el-form-item__label {
float: none;
display: block;
text-align: left;
padding: 0 0 8px;
line-height: 1.5;
}
.el-form-item__content {
margin-left: 0 !important;
}
/* 搜索表单适配 */
.search-form .el-form-item {
display: block;
margin-right: 0;
}
.search-form .el-form-item__content {
width: 100%;
}
.search-form .el-input,
.search-form .el-select {
width: 100%;
}
/* 卡片适配 */
.el-card {
margin-bottom: 10px;
}
.el-card__header {
padding: 12px 15px;
}
.el-card__body {
padding: 15px;
}
/* 分页适配 */
.el-pagination {
text-align: center;
padding: 10px 0;
}
.el-pagination .el-pagination__total,
.el-pagination .el-pagination__sizes,
.el-pagination .el-pagination__jump {
display: none;
}
/* 按钮组适配 */
.el-button + .el-button {
margin-left: 5px;
}
/* 对话框适配 */
.el-dialog {
width: 90% !important;
margin-top: 10vh !important;
}
.el-dialog__body {
padding: 15px;
}
}
/* 小屏幕手机适配 */
@media screen and (max-width: 375px) {
.app-title {
font-size: 14px;
}
.app-main {
padding: 8px;
}
.el-card__body {
padding: 12px;
}
}
</style>

View File

@ -3,6 +3,7 @@ import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import './styles/responsive.css'
Vue.use(ElementUI)
@ -11,4 +12,4 @@ Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
}).$mount('#app')

152
src/styles/responsive.css Normal file
View File

@ -0,0 +1,152 @@
/* 全局响应式样式 */
/* 移动端隐藏类 */
.hidden-xs-only {
display: block;
}
.hidden-sm-and-up {
display: none;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.hidden-xs-only {
display: none !important;
}
.hidden-sm-and-up {
display: block !important;
}
/* 表单适配 */
.search-form .el-form-item {
display: block;
margin-right: 0;
margin-bottom: 10px;
}
.search-form .el-form-item__content {
width: 100%;
}
.search-form .el-input,
.search-form .el-select {
width: 100%;
}
/* 表格横向滚动 */
.table-wrapper {
overflow-x: auto;
}
.el-table {
min-width: 600px;
}
/* 分页适配 */
.pagination-wrapper,
.pagination {
justify-content: center;
}
/* 卡片头部适配 */
.card-header {
flex-wrap: wrap;
gap: 10px;
}
.card-header span {
font-size: 16px;
}
/* 移动端卡片列表样式 */
.mobile-list {
display: block;
}
.mobile-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 15px;
margin-bottom: 10px;
}
.mobile-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
.mobile-card-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.mobile-card-id {
font-size: 12px;
color: #909399;
}
.mobile-card-body {
margin-bottom: 10px;
}
.mobile-card-item {
display: flex;
margin-bottom: 8px;
font-size: 14px;
}
.mobile-card-label {
color: #909399;
min-width: 70px;
flex-shrink: 0;
}
.mobile-card-value {
color: #606266;
flex: 1;
word-break: break-all;
}
.mobile-card-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding-top: 10px;
border-top: 1px solid #ebeef5;
}
/* 对话框适配 */
.el-dialog {
width: 90% !important;
margin-top: 10vh !important;
}
.el-dialog__body {
padding: 15px;
}
}
@media screen and (min-width: 769px) {
.hidden-sm-and-up {
display: none !important;
}
}
/* 小屏幕手机适配 */
@media screen and (max-width: 375px) {
.mobile-card {
padding: 12px;
}
.mobile-card-title {
font-size: 14px;
}
}

View File

@ -6,10 +6,10 @@
</el-card>
<el-row :gutter="20" style="margin-top: 20px;">
<el-col :xs="24" :sm="12" :md="8">
<el-col :xs="12" :sm="12" :md="8">
<el-card class="stat-card">
<div class="stat-item">
<el-icon class="el-icon-location"></el-icon>
<i class="el-icon-location stat-icon"></i>
<div class="stat-info">
<div class="stat-value">{{ regionCount }}</div>
<div class="stat-label">区域数量</div>
@ -17,10 +17,10 @@
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-col :xs="12" :sm="12" :md="8">
<el-card class="stat-card">
<div class="stat-item">
<el-icon class="el-icon-building"></el-icon>
<i class="el-icon-office-building stat-icon"></i>
<div class="stat-info">
<div class="stat-value">{{ apartmentCount }}</div>
<div class="stat-label">公寓数量</div>
@ -28,10 +28,10 @@
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-col :xs="12" :sm="12" :md="8">
<el-card class="stat-card">
<div class="stat-item">
<el-icon class="el-icon-home"></el-icon>
<i class="el-icon-menu stat-icon"></i>
<div class="stat-info">
<div class="stat-value">{{ roomCount }}</div>
<div class="stat-label">房间数量</div>
@ -40,10 +40,10 @@
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-col :xs="12" :sm="12" :md="8">
<el-card class="stat-card">
<div class="stat-item">
<el-icon class="el-icon-s-finance"></el-icon>
<i class="el-icon-house stat-icon"></i>
<div class="stat-info">
<div class="stat-value">{{ emptyRoomCount }}</div>
<div class="stat-label">空房数量</div>
@ -51,10 +51,10 @@
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-col :xs="12" :sm="12" :md="8">
<el-card class="stat-card">
<div class="stat-item">
<el-icon class="el-icon-s-finance"></el-icon>
<i class="el-icon-key stat-icon"></i>
<div class="stat-info">
<div class="stat-value">{{ rentedRoomCount }}</div>
<div class="stat-label">在租数量</div>
@ -62,10 +62,10 @@
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-col :xs="12" :sm="12" :md="8">
<el-card class="stat-card">
<div class="stat-item">
<el-icon class="el-icon-s-finance"></el-icon>
<i class="el-icon-time stat-icon"></i>
<div class="stat-info">
<div class="stat-value">{{ soonExpireRoomCount }}</div>
<div class="stat-label">即将到期</div>
@ -73,10 +73,10 @@
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-col :xs="12" :sm="12" :md="8">
<el-card class="stat-card">
<div class="stat-item">
<el-icon class="el-icon-s-finance"></el-icon>
<i class="el-icon-warning stat-icon"></i>
<div class="stat-info">
<div class="stat-value">{{ expiredRoomCount }}</div>
<div class="stat-label">已到期</div>
@ -84,23 +84,23 @@
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-col :xs="12" :sm="12" :md="8">
<el-card class="stat-card">
<div class="stat-item">
<el-icon class="el-icon-s-finance"></el-icon>
<i class="el-icon-money stat-icon"></i>
<div class="stat-info">
<div class="stat-value">¥{{ collectedRentAmount }}</div>
<div class="stat-value">{{ formatMoney(collectedRentAmount) }}</div>
<div class="stat-label">已收租金</div>
</div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="8">
<el-col :xs="12" :sm="12" :md="8">
<el-card class="stat-card">
<div class="stat-item">
<el-icon class="el-icon-s-finance"></el-icon>
<i class="el-icon-water-cup stat-icon"></i>
<div class="stat-info">
<div class="stat-value">¥{{ collectedWaterAmount }}</div>
<div class="stat-value">{{ formatMoney(collectedWaterAmount) }}</div>
<div class="stat-label">已收水费</div>
</div>
</div>
@ -108,23 +108,31 @@
</el-col>
</el-row>
<el-card style="margin-top: 20px;">
<el-card class="stats-table-card">
<template slot="header">
<div class="card-header">
<span>区域公寓房间状态分布</span>
</div>
</template>
<el-table :data="regionApartmentHouseStats" style="width: 100%" show-summary :summary-method="getSummary">
<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>
<div class="table-wrapper">
<el-table
:data="regionApartmentHouseStats"
style="width: 100%"
show-summary
:summary-method="getSummary"
class="stats-table"
>
<el-table-column prop="region" label="区域" min-width="100"></el-table-column>
<el-table-column prop="apartment" label="公寓" min-width="100"></el-table-column>
<el-table-column prop="empty" label="空房" width="70"></el-table-column>
<el-table-column prop="rented" label="在租" width="70"></el-table-column>
<el-table-column prop="soon_expire" label="即将到期" width="90"></el-table-column>
<el-table-column prop="expired" label="已到期" width="70"></el-table-column>
<el-table-column prop="cleaning" label="打扫中" width="70"></el-table-column>
<el-table-column prop="maintenance" label="维修中" width="70"></el-table-column>
<el-table-column prop="total" label="总数" width="70"></el-table-column>
</el-table>
</div>
</el-card>
</div>
</template>
@ -178,6 +186,12 @@ export default {
this.$message.error('加载数据失败')
}
},
formatMoney(value) {
if (value >= 10000) {
return '¥' + (value / 10000).toFixed(1) + '万'
}
return '¥' + value
},
getSummary(param) {
const { columns, data } = param;
const sums = [];
@ -208,32 +222,39 @@ export default {
<style scoped>
.dashboard {
padding: 20px 0;
padding: 0;
}
.welcome-card {
text-align: center;
padding: 40px 0;
padding: 30px 0;
}
.welcome-card h2 {
margin: 0 0 10px 0;
font-size: 24px;
color: #303133;
}
.welcome-card p {
margin: 0;
color: #606266;
}
.stat-card {
height: 120px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.stat-item {
display: flex;
align-items: center;
width: 100%;
padding: 0 20px;
padding: 10px;
}
.stat-item .el-icon {
.stat-icon {
font-size: 36px;
color: #409EFF;
margin-right: 20px;
margin-right: 15px;
}
.stat-info {
@ -241,14 +262,14 @@ export default {
}
.stat-value {
font-size: 24px;
font-size: 22px;
font-weight: bold;
color: #333;
color: #303133;
}
.stat-label {
font-size: 14px;
color: #666;
font-size: 13px;
color: #909399;
margin-top: 4px;
}
@ -257,4 +278,77 @@ export default {
justify-content: space-between;
align-items: center;
}
</style>
.stats-table-card {
margin-top: 20px;
}
.table-wrapper {
overflow-x: auto;
}
.stats-table {
min-width: 800px;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.welcome-card {
padding: 20px 0;
}
.welcome-card h2 {
font-size: 18px;
}
.welcome-card p {
font-size: 13px;
}
.stat-card {
margin-bottom: 10px;
}
.stat-item {
padding: 5px;
flex-direction: column;
text-align: center;
}
.stat-icon {
font-size: 24px;
margin-right: 0;
margin-bottom: 8px;
}
.stat-value {
font-size: 16px;
}
.stat-label {
font-size: 12px;
}
.stats-table-card {
margin-top: 10px;
}
}
@media screen and (max-width: 375px) {
.welcome-card h2 {
font-size: 16px;
}
.stat-icon {
font-size: 20px;
}
.stat-value {
font-size: 14px;
}
.stat-label {
font-size: 11px;
}
}
</style>

View File

@ -6,9 +6,9 @@
<span>添加公寓</span>
</div>
</template>
<el-form :model="apartmentForm" :rules="rules" ref="apartmentForm" label-width="100px">
<el-form :model="apartmentForm" :rules="rules" ref="apartmentForm" label-width="100px" class="form-content">
<el-form-item label="区域" prop="regionId">
<el-select v-model="apartmentForm.regionId" placeholder="请选择区域">
<el-select v-model="apartmentForm.regionId" placeholder="请选择区域" style="width: 100%">
<el-option v-for="region in regions" :key="region.id" :label="region.name" :value="region.id"></el-option>
</el-select>
</el-form-item>
@ -18,7 +18,7 @@
<el-form-item label="地址" prop="address">
<el-input v-model="apartmentForm.address" placeholder="请输入地址"></el-input>
</el-form-item>
<el-form-item>
<el-form-item class="form-actions">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
<el-button @click="goBack">返回</el-button>
@ -90,7 +90,7 @@ export default {
<style scoped>
.apartment-add {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -98,4 +98,38 @@ export default {
justify-content: space-between;
align-items: center;
}
</style>
.form-content {
max-width: 600px;
}
.form-actions {
margin-top: 30px;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.form-content {
max-width: 100%;
}
.form-content .el-form-item__label {
float: none;
display: block;
text-align: left;
margin-bottom: 5px;
}
.form-content .el-form-item__content {
margin-left: 0 !important;
}
.form-actions {
text-align: center;
}
.form-actions .el-button {
margin-bottom: 10px;
}
}
</style>

View File

@ -6,9 +6,9 @@
<span>编辑公寓</span>
</div>
</template>
<el-form :model="apartmentForm" :rules="rules" ref="apartmentForm" label-width="100px">
<el-form :model="apartmentForm" :rules="rules" ref="apartmentForm" label-width="100px" class="form-content">
<el-form-item label="区域" prop="regionId">
<el-select v-model="apartmentForm.regionId" placeholder="请选择区域">
<el-select v-model="apartmentForm.regionId" placeholder="请选择区域" style="width: 100%">
<el-option v-for="region in regions" :key="region.id" :label="region.name" :value="region.id"></el-option>
</el-select>
</el-form-item>
@ -18,7 +18,7 @@
<el-form-item label="地址" prop="address">
<el-input v-model="apartmentForm.address" placeholder="请输入地址"></el-input>
</el-form-item>
<el-form-item>
<el-form-item class="form-actions">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
<el-button @click="goBack">返回</el-button>
@ -103,7 +103,7 @@ export default {
<style scoped>
.apartment-edit {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -111,4 +111,38 @@ export default {
justify-content: space-between;
align-items: center;
}
</style>
.form-content {
max-width: 600px;
}
.form-actions {
margin-top: 30px;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.form-content {
max-width: 100%;
}
.form-content .el-form-item__label {
float: none;
display: block;
text-align: left;
margin-bottom: 5px;
}
.form-content .el-form-item__content {
margin-left: 0 !important;
}
.form-actions {
text-align: center;
}
.form-actions .el-button {
margin-bottom: 10px;
}
}
</style>

View File

@ -4,12 +4,14 @@
<template slot="header">
<div class="card-header">
<span>公寓管理</span>
<el-button type="primary" @click="handleAdd">添加公寓</el-button>
<el-button type="primary" size="small" @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-select v-model="searchForm.regionId" placeholder="请选择区域" style="width: 100%">
<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>
@ -22,22 +24,55 @@
<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;">
<!-- PC端表格 -->
<div class="table-wrapper hidden-xs-only">
<el-table :data="apartments" style="width: 100%" v-loading="isLoading">
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="regionName" label="区域" min-width="100"></el-table-column>
<el-table-column prop="name" label="公寓名称" min-width="120"></el-table-column>
<el-table-column prop="address" label="地址" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="handleEdit(scope.row.id)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 移动端卡片列表 -->
<div class="mobile-list hidden-sm-and-up">
<div v-for="item in apartments" :key="item.id" class="mobile-card">
<div class="mobile-card-header">
<span class="mobile-card-title">{{ item.name }}</span>
<span class="mobile-card-id">ID: {{ item.id }}</span>
</div>
<div class="mobile-card-body">
<div class="mobile-card-item">
<span class="mobile-card-label">区域:</span>
<span class="mobile-card-value">{{ item.regionName || '-' }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">地址:</span>
<span class="mobile-card-value">{{ item.address || '-' }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">创建时间:</span>
<span class="mobile-card-value">{{ item.createTime }}</span>
</div>
</div>
<div class="mobile-card-footer">
<el-button size="mini" type="primary" @click="handleEdit(item.id)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(item.id)">删除</el-button>
</div>
</div>
</div>
<div class="pagination-wrapper">
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:layout="paginationLayout"
:total="total"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
@ -65,14 +100,30 @@ export default {
name: ''
},
currentPage: 1,
pageSize: 10
pageSize: 10,
isLoading: false,
isMobile: false
}
},
computed: {
paginationLayout() {
return this.isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'
}
},
mounted() {
this.checkDevice()
window.addEventListener('resize', this.checkDevice)
this.loadData()
},
beforeDestroy() {
window.removeEventListener('resize', this.checkDevice)
},
methods: {
checkDevice() {
this.isMobile = window.innerWidth <= 768
},
async loadData() {
this.isLoading = true
try {
//
const regionsResponse = await regionApi.getAll()
@ -98,6 +149,8 @@ export default {
this.total = apartmentsResponse.total
} catch (error) {
this.$message.error('加载数据失败')
} finally {
this.isLoading = false
}
},
handleAdd() {
@ -137,12 +190,12 @@ export default {
},
handleCurrentChange(val) {
this.currentPage = val
this.loadApartments()
this.loadData()
},
handleSizeChange(val) {
this.pageSize = val
this.currentPage = 1
this.loadApartments()
this.loadData()
}
}
}
@ -150,7 +203,7 @@ export default {
<style scoped>
.apartment-list {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -163,8 +216,125 @@ export default {
margin-bottom: 20px;
}
.pagination {
.table-wrapper {
overflow-x: auto;
}
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
/* 移动端卡片列表样式 */
.mobile-list {
display: none;
}
.mobile-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 15px;
margin-bottom: 10px;
}
.mobile-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
.mobile-card-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.mobile-card-id {
font-size: 12px;
color: #909399;
}
.mobile-card-body {
margin-bottom: 10px;
}
.mobile-card-item {
display: flex;
margin-bottom: 8px;
font-size: 14px;
}
.mobile-card-label {
color: #909399;
min-width: 70px;
flex-shrink: 0;
}
.mobile-card-value {
color: #606266;
flex: 1;
word-break: break-all;
}
.mobile-card-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding-top: 10px;
border-top: 1px solid #ebeef5;
}
/* 响应式显示控制 */
@media screen and (max-width: 768px) {
.hidden-xs-only {
display: none;
}
.mobile-list {
display: block;
}
.search-form {
margin-bottom: 15px;
}
.search-form .el-form-item {
display: block;
margin-right: 0;
margin-bottom: 10px;
}
.search-form .el-form-item__content {
width: 100%;
}
.search-form .el-input,
.search-form .el-select {
width: 100%;
}
.pagination-wrapper {
justify-content: center;
}
.card-header {
flex-wrap: wrap;
gap: 10px;
}
.card-header span {
font-size: 16px;
}
}
@media screen and (min-width: 769px) {
.hidden-sm-and-up {
display: none;
}
}
</style>

View File

@ -6,14 +6,14 @@
<span>添加区域</span>
</div>
</template>
<el-form :model="regionForm" :rules="rules" ref="regionForm" label-width="100px">
<el-form :model="regionForm" :rules="rules" ref="regionForm" label-width="100px" class="form-content">
<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-input type="textarea" v-model="regionForm.description" placeholder="请输入区域描述" rows="4"></el-input>
</el-form-item>
<el-form-item>
<el-form-item class="form-actions">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
<el-button @click="goBack">返回</el-button>
@ -69,7 +69,7 @@ export default {
<style scoped>
.region-add {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -77,4 +77,38 @@ export default {
justify-content: space-between;
align-items: center;
}
</style>
.form-content {
max-width: 600px;
}
.form-actions {
margin-top: 30px;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.form-content {
max-width: 100%;
}
.form-content .el-form-item__label {
float: none;
display: block;
text-align: left;
margin-bottom: 5px;
}
.form-content .el-form-item__content {
margin-left: 0 !important;
}
.form-actions {
text-align: center;
}
.form-actions .el-button {
margin-bottom: 10px;
}
}
</style>

View File

@ -6,14 +6,14 @@
<span>编辑区域</span>
</div>
</template>
<el-form :model="regionForm" :rules="rules" ref="regionForm" label-width="100px">
<el-form :model="regionForm" :rules="rules" ref="regionForm" label-width="100px" class="form-content">
<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-input type="textarea" v-model="regionForm.description" placeholder="请输入区域描述" rows="4"></el-input>
</el-form-item>
<el-form-item>
<el-form-item class="form-actions">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
<el-button @click="goBack">返回</el-button>
@ -84,7 +84,7 @@ export default {
<style scoped>
.region-edit {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -92,4 +92,38 @@ export default {
justify-content: space-between;
align-items: center;
}
</style>
.form-content {
max-width: 600px;
}
.form-actions {
margin-top: 30px;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.form-content {
max-width: 100%;
}
.form-content .el-form-item__label {
float: none;
display: block;
text-align: left;
margin-bottom: 5px;
}
.form-content .el-form-item__content {
margin-left: 0 !important;
}
.form-actions {
text-align: center;
}
.form-actions .el-button {
margin-bottom: 10px;
}
}
</style>

View File

@ -4,24 +4,53 @@
<template slot="header">
<div class="card-header">
<span>区域列表</span>
<el-button type="primary" @click="handleAdd">添加区域</el-button>
<el-button type="primary" size="small" @click="handleAdd">添加区域</el-button>
</div>
</template>
<el-table :data="regions" style="width: 100%" v-loading="isLoading">
<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;">
<!-- PC端表格 -->
<div class="table-wrapper hidden-xs-only">
<el-table :data="regions" style="width: 100%" v-loading="isLoading">
<el-table-column prop="id" label="ID" width="80"></el-table-column>
<el-table-column prop="name" label="区域名称" min-width="120"></el-table-column>
<el-table-column prop="description" label="区域描述" min-width="200" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="handleEdit(scope.row.id)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 移动端卡片列表 -->
<div class="mobile-list hidden-sm-and-up">
<div v-for="item in regions" :key="item.id" class="mobile-card">
<div class="mobile-card-header">
<span class="mobile-card-title">{{ item.name }}</span>
<span class="mobile-card-id">ID: {{ item.id }}</span>
</div>
<div class="mobile-card-body">
<div class="mobile-card-item">
<span class="mobile-card-label">描述:</span>
<span class="mobile-card-value">{{ item.description || '-' }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">创建时间:</span>
<span class="mobile-card-value">{{ item.createTime }}</span>
</div>
</div>
<div class="mobile-card-footer">
<el-button size="mini" type="primary" @click="handleEdit(item.id)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(item.id)">删除</el-button>
</div>
</div>
</div>
<div class="pagination-wrapper">
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:layout="paginationLayout"
:total="total"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
@ -45,13 +74,27 @@ export default {
total: 0,
currentPage: 1,
pageSize: 10,
isLoading: false
isLoading: false,
isMobile: false
}
},
computed: {
paginationLayout() {
return this.isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'
}
},
mounted() {
this.checkDevice()
window.addEventListener('resize', this.checkDevice)
this.loadRegions()
},
beforeDestroy() {
window.removeEventListener('resize', this.checkDevice)
},
methods: {
checkDevice() {
this.isMobile = window.innerWidth <= 768
},
async loadRegions() {
this.isLoading = true
try {
@ -106,7 +149,7 @@ export default {
<style scoped>
.region-list {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -115,8 +158,106 @@ export default {
align-items: center;
}
.pagination {
.table-wrapper {
overflow-x: auto;
}
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
</style>
/* 移动端卡片列表样式 */
.mobile-list {
display: none;
}
.mobile-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 15px;
margin-bottom: 10px;
}
.mobile-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
}
.mobile-card-title {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.mobile-card-id {
font-size: 12px;
color: #909399;
}
.mobile-card-body {
margin-bottom: 10px;
}
.mobile-card-item {
display: flex;
margin-bottom: 8px;
font-size: 14px;
}
.mobile-card-label {
color: #909399;
min-width: 70px;
flex-shrink: 0;
}
.mobile-card-value {
color: #606266;
flex: 1;
word-break: break-all;
}
.mobile-card-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding-top: 10px;
border-top: 1px solid #ebeef5;
}
/* 响应式显示控制 */
@media screen and (max-width: 768px) {
.hidden-xs-only {
display: none;
}
.mobile-list {
display: block;
}
.pagination-wrapper {
justify-content: center;
}
.card-header {
flex-wrap: wrap;
gap: 10px;
}
.card-header span {
font-size: 16px;
}
}
@media screen and (min-width: 769px) {
.hidden-sm-and-up {
display: none;
}
}
</style>

View File

@ -6,7 +6,7 @@
<span>添加租房</span>
</div>
</template>
<el-form :model="rentalForm" :rules="rules" ref="rentalForm" label-width="120px">
<el-form :model="rentalForm" :rules="rules" ref="rentalForm" label-width="120px" class="form-content">
<el-form-item label="房间" prop="roomId">
<el-select v-model="rentalForm.roomId" placeholder="请选择房间" style="width: 100%">
<el-option
@ -64,7 +64,7 @@
<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-form-item class="form-actions">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
<el-button @click="goBack">返回</el-button>
@ -91,7 +91,6 @@ export default {
status: 'active',
remark: ''
},
//
returnQuery: {},
rules: {
roomId: [{ required: true, message: '请选择房间', trigger: 'blur' }],
@ -121,18 +120,14 @@ export default {
}
},
mounted() {
//
this.returnQuery = this.$route.query
//
this.rentalForm.startDate = new Date()
//
this.updateEndDate()
this.loadData()
},
methods: {
async loadData() {
try {
// 使list
const [roomsResponse, apartmentsResponse] = await Promise.all([
roomApi.list(),
apartmentApi.list()
@ -141,16 +136,11 @@ export default {
this.rooms = roomsResponse
this.apartments = apartmentsResponse
// URLroomId
const roomId = this.$route.query.roomId ? Number(this.$route.query.roomId) : null
if (roomId) {
// roomIdoptionvalue
this.rentalForm.roomId = roomId
//
const room = this.rooms.find(r => r.id == roomId)
if (room) {
//
this.rentalForm.rent = room.monthlyPrice
}
}
@ -171,14 +161,12 @@ export default {
try {
await rentalApi.create(this.rentalForm)
this.$message.success('添加成功')
//
if (this.rentalForm.roomId) {
this.$router.push({
path: `/rental/detail/${this.rentalForm.roomId}`,
query: this.returnQuery
})
} else {
// ID
this.$router.push({
path: '/rental/list',
query: this.returnQuery
@ -216,7 +204,7 @@ export default {
<style scoped>
.rental-add {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -224,4 +212,42 @@ export default {
justify-content: space-between;
align-items: center;
}
</style>
.form-content {
max-width: 600px;
}
.form-actions {
margin-top: 30px;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.form-content {
max-width: 100%;
}
.form-content .el-form-item__label {
float: none;
display: block;
text-align: left;
margin-bottom: 5px;
}
.form-content .el-form-item__content {
margin-left: 0 !important;
}
.form-actions {
text-align: center;
}
.form-actions .el-button {
margin-bottom: 10px;
}
.el-divider__text {
font-size: 14px;
}
}
</style>

View File

@ -5,13 +5,13 @@
<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.otherStatus || room.otherStatus === ''" type="info" @click="handleCleaning">打扫</el-button>
<el-button v-if="!room.otherStatus || room.otherStatus === ''" type="danger" @click="handleMaintenance">维修</el-button>
<el-button v-if="room.otherStatus === 'cleaning'" type="success" @click="handleComplete">打扫完成</el-button>
<el-button v-if="room.otherStatus === 'maintenance'" type="success" @click="handleComplete">维修完成</el-button>
<el-button type="primary" @click="goBack">返回</el-button>
<el-button v-if="room.status === 'empty'" type="primary" size="small" @click="handleRent">租房</el-button>
<el-button v-if="room.status === 'rented'" type="warning" size="small" @click="handleCheckout">退房</el-button>
<el-button v-if="!room.otherStatus || room.otherStatus === ''" type="info" size="small" @click="handleCleaning">打扫</el-button>
<el-button v-if="!room.otherStatus || room.otherStatus === ''" type="danger" size="small" @click="handleMaintenance">维修</el-button>
<el-button v-if="room.otherStatus === 'cleaning'" type="success" size="small" @click="handleComplete">打扫完成</el-button>
<el-button v-if="room.otherStatus === 'maintenance'" type="success" size="small" @click="handleComplete">维修完成</el-button>
<el-button type="primary" size="small" @click="goBack">返回</el-button>
</div>
</div>
</template>
@ -26,15 +26,15 @@
<span class="label">租金:</span>
<span class="value">
¥{{ room.monthlyPrice }}/
<span v-if="room.yearlyPrice" style="margin-left: 10px;">¥{{ room.yearlyPrice }}/</span>
<span v-if="room.yearlyPrice" class="yearly-price">¥{{ room.yearlyPrice }}/</span>
</span>
</div>
<div class="info-item">
<div class="info-item status-item">
<span class="label">状态:</span>
<span class="value" style="display: flex; gap: 8px;">
<el-tag :type="getStatusType(room.status)">{{ getStatusText(room.status) }}</el-tag>
<el-tag v-if="room.status === 'rented' && room.subStatus" :type="getSubStatusType(room.subStatus)">{{ getSubStatusText(room.subStatus) }}</el-tag>
<el-tag v-if="room.otherStatus && room.otherStatus !== ''" :type="getOtherStatusType(room.otherStatus)">{{ getOtherStatusText(room.otherStatus) }}</el-tag>
<span class="value">
<el-tag :type="getStatusType(room.status)" size="small">{{ getStatusText(room.status) }}</el-tag>
<el-tag v-if="room.status === 'rented' && room.subStatus" :type="getSubStatusType(room.subStatus)" size="small">{{ getSubStatusText(room.subStatus) }}</el-tag>
<el-tag v-if="room.otherStatus && room.otherStatus !== ''" :type="getOtherStatusType(room.otherStatus)" size="small">{{ getOtherStatusText(room.otherStatus) }}</el-tag>
</span>
</div>
<div class="info-item">
@ -46,37 +46,39 @@
<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-column label="操作" width="220">
<template slot-scope="scope">
<el-button type="primary" size="small" @click="handleEditRental(scope.row)">编辑</el-button>
<el-button type="success" size="small" @click="handleRenewRental(scope.row)">续租</el-button>
<el-button type="danger" size="small" @click="handleDeleteRental(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
<div class="table-wrapper">
<el-table :data="rentalHistory" style="width: 100%" class="detail-table">
<el-table-column prop="tenantName" label="租客" min-width="100"></el-table-column>
<el-table-column prop="startDate" label="开始日期" min-width="100"></el-table-column>
<el-table-column prop="endDate" label="结束日期" min-width="100"></el-table-column>
<el-table-column prop="rent" label="租金(元/月)" min-width="100"></el-table-column>
<el-table-column prop="deposit" label="押金(元)" min-width="90"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="120"></el-table-column>
<el-table-column prop="status" label="状态" min-width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'" size="small">
{{ scope.row.status === 'active' ? '在租' : '已到期' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="140"></el-table-column>
<el-table-column label="操作" min-width="180" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleEditRental(scope.row)">编辑</el-button>
<el-button type="success" size="mini" @click="handleRenewRental(scope.row)">续租</el-button>
<el-button type="danger" size="mini" @click="handleDeleteRental(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-wrapper">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:layout="isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
:total="total">
</el-pagination>
</div>
@ -85,46 +87,51 @@
<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>
<div style="margin-top: 20px; display: flex; justify-content: flex-end;">
<div class="table-wrapper">
<el-table :data="waterBills" style="width: 100%" class="detail-table">
<el-table-column prop="startDate" label="开始日期" min-width="100"></el-table-column>
<el-table-column prop="endDate" label="结束日期" min-width="100"></el-table-column>
<el-table-column prop="startReading" label="起始度数" min-width="90"></el-table-column>
<el-table-column prop="endReading" label="结束度数" min-width="90"></el-table-column>
<el-table-column prop="usage" label="用水量(吨)" min-width="100"></el-table-column>
<el-table-column prop="unitPrice" label="单价(元/吨)" min-width="100"></el-table-column>
<el-table-column prop="amount" label="费用(元)" min-width="90">
<template slot-scope="scope">
<span class="amount-value">¥{{ scope.row.amount }}</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" min-width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 'paid' ? 'success' : 'warning'" size="small">
{{ scope.row.status === 'paid' ? '已支付' : '未支付' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="140" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleEditWaterBill(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDeleteWaterBill(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-wrapper">
<el-pagination
@size-change="handleWaterSizeChange"
@current-change="handleWaterCurrentChange"
:current-page="waterCurrentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="waterPageSize"
layout="total, sizes, prev, pager, next, jumper"
:layout="isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
:total="waterTotal">
</el-pagination>
</div>
</el-tab-pane>
</el-tabs>
<!-- 水费编辑对话框 -->
<el-dialog title="编辑水费" :visible.sync="waterBillDialogVisible" width="500px">
<el-form :model="waterBillForm" :rules="waterBillRules" ref="waterBillForm">
<el-form :model="waterBillForm" :rules="waterBillRules" ref="waterBillForm" label-width="90px">
<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>
@ -141,7 +148,7 @@
<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-select v-model="waterBillForm.status" placeholder="请选择状态" style="width: 100%">
<el-option label="未支付" value="unpaid"></el-option>
<el-option label="已支付" value="paid"></el-option>
</el-select>
@ -153,11 +160,9 @@
</span>
</el-dialog>
<!-- 租赁编辑对话框 -->
<el-dialog :title="rentalForm.id ? '编辑租赁记录' : '新增租赁记录'" :visible.sync="rentalDialogVisible" width="500px">
<el-form :model="rentalForm" :rules="rentalRules" ref="rentalForm">
<el-form :model="rentalForm" :rules="rentalRules" ref="rentalForm" label-width="80px">
<el-form-item label="租客姓名" prop="tenantName">
<el-input v-model="rentalForm.tenantName" placeholder="请输入租客姓名"></el-input>
</el-form-item>
@ -205,13 +210,10 @@ export default {
waterBills: [],
isLoading: false,
activeTab: 'rental',
//
returnQuery: {},
// -
currentPage: 1,
pageSize: 10,
total: 0,
// -
waterCurrentPage: 1,
waterPageSize: 10,
waterTotal: 0,
@ -255,14 +257,21 @@ export default {
}
}
},
computed: {
isMobile() {
return window.innerWidth <= 768
}
},
mounted() {
//
this.returnQuery = this.$route.query
this.loadData()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
},
watch: {
activeTab: function() {
// tab
if (this.activeTab === 'rental') {
this.loadRentalHistory()
} else if (this.activeTab === 'water') {
@ -271,19 +280,16 @@ export default {
}
},
methods: {
handleResize() {
this.$forceUpdate()
},
async loadData() {
this.isLoading = true
try {
const roomId = this.$route.params.id
//
const roomResponse = await roomApi.getById(roomId)
this.room = roomResponse
//
await this.loadRentalHistory()
// tab
if (this.activeTab === 'water') {
await this.loadWaterBills()
}
@ -297,17 +303,12 @@ export default {
this.isLoading = true
try {
const roomId = this.$route.params.id
// API
const response = await waterBillApi.getAll({
roomId,
page: this.waterCurrentPage,
pageSize: this.waterPageSize
})
//
this.waterBills = response.data || response
//
this.waterTotal = response.total || 0
} catch (error) {
this.$message.error('加载水费记录失败')
@ -319,22 +320,13 @@ export default {
this.isLoading = true
try {
const roomId = this.$route.params.id
// API
const response = await rentalApi.getAll({
roomId,
page: this.currentPage,
pageSize: this.pageSize
})
//
const data = response.data || response
this.rentalHistory = data.map(rental => {
return {
...rental
}
})
//
this.rentalHistory = data.map(rental => ({ ...rental }))
this.total = response.total || 0
} catch (error) {
this.$message.error('加载租赁历史失败')
@ -433,17 +425,12 @@ export default {
type: 'warning'
}).then(async () => {
try {
//
const roomId = this.$route.params.id
const rental = this.rentalHistory.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 {
@ -453,9 +440,7 @@ export default {
console.error('退房失败:', error)
this.$message.error('退房失败:' + (error.message || '未知错误'))
}
}).catch(() => {
// 退
})
}).catch(() => {})
},
async handleCleaning() {
this.$confirm('确定要标记为打扫中吗?', '提示', {
@ -471,9 +456,7 @@ export default {
} catch (error) {
this.$message.error('操作失败')
}
}).catch(() => {
//
})
}).catch(() => {})
},
async handleMaintenance() {
this.$confirm('确定要标记为维修中吗?', '提示', {
@ -489,9 +472,7 @@ export default {
} catch (error) {
this.$message.error('操作失败')
}
}).catch(() => {
//
})
}).catch(() => {})
},
async handleComplete() {
this.$confirm('确定要完成打扫/维修吗?', '提示', {
@ -507,9 +488,7 @@ export default {
} catch (error) {
this.$message.error('操作失败')
}
}).catch(() => {
//
})
}).catch(() => {})
},
handleAddWaterBill() {
this.waterBillForm = {
@ -536,15 +515,10 @@ export default {
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
})
await waterBillApi.create({ ...this.waterBillForm, roomId })
this.$message.success('水费记录添加成功')
}
this.waterBillDialogVisible = false
@ -566,11 +540,8 @@ export default {
} catch (error) {
this.$message.error('删除失败')
}
}).catch(() => {
//
})
}).catch(() => {})
},
handleEditRental(rental) {
this.rentalForm = {
...rental,
@ -580,19 +551,13 @@ export default {
this.rentalDialogVisible = true
},
handleRenewRental(rental) {
//
const oldEndDate = new Date(rental.endDate)
const newStartDate = new Date(oldEndDate)
//
const oldStartDate = new Date(rental.startDate)
const monthsDiff = (oldEndDate.getFullYear() - oldStartDate.getFullYear()) * 12 + (oldEndDate.getMonth() - oldStartDate.getMonth())
//
const newEndDate = new Date(newStartDate)
newEndDate.setMonth(newEndDate.getMonth() + monthsDiff)
// ID
this.rentalForm = {
id: '',
roomId: rental.roomId,
@ -611,11 +576,9 @@ export default {
async handleSaveRental() {
try {
if (this.rentalForm.id) {
//
await rentalApi.update(this.rentalForm.id, this.rentalForm)
this.$message.success('租赁记录更新成功')
} else {
//
await rentalApi.create(this.rentalForm)
this.$message.success('租赁记录添加成功')
}
@ -632,36 +595,22 @@ export default {
type: 'danger'
}).then(async () => {
try {
//
const rental = this.rentalHistory.find(r => r.id == id)
//
await rentalApi.delete(id)
this.$message.success('租赁记录删除成功')
//
if (rental && rental.status === 'active' && rental.roomId) {
await roomApi.update(rental.roomId, {
status: 'empty',
subStatus: 'normal'
})
await roomApi.update(rental.roomId, { status: 'empty', subStatus: 'normal' })
this.$message.success('房间状态已更新为空房')
}
this.loadData()
} catch (error) {
console.error('删除失败:', error)
this.$message.error('删除失败:' + (error.message || '未知错误'))
}
}).catch(() => {
//
})
}).catch(() => {})
},
goBack() {
this.$router.push({
path: '/rental/list',
query: this.returnQuery
})
this.$router.push({ path: '/rental/list', query: this.returnQuery })
}
}
}
@ -669,13 +618,15 @@ export default {
<style scoped>
.rental-detail {
padding: 20px 0;
padding: 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.room-info-section {
@ -704,34 +655,36 @@ export default {
.info-item .label {
width: 80px;
color: #606266;
flex-shrink: 0;
}
.info-item .value {
font-weight: 500;
}
.rental-history-section {
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #e4e7ed;
.yearly-price {
margin-left: 10px;
color: #909399;
}
.rental-history-section h3 {
margin-bottom: 15px;
font-size: 18px;
font-weight: 500;
.status-item .value {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.water-bill-section {
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #e4e7ed;
.table-wrapper {
overflow-x: auto;
}
.electricity-bill-section {
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 1px solid #e4e7ed;
.detail-table {
min-width: 800px;
}
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.section-header {
@ -741,14 +694,67 @@ export default {
margin-bottom: 15px;
}
.section-header h3 {
font-size: 18px;
font-weight: 500;
margin: 0;
}
.action-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.amount-value {
color: #f56c6c;
font-weight: bold;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.card-header {
flex-direction: column;
align-items: flex-start;
}
.card-header span {
font-size: 16px;
margin-bottom: 10px;
}
.action-buttons {
width: 100%;
justify-content: flex-start;
}
.room-info-section h2 {
font-size: 18px;
}
.room-basic-info {
grid-template-columns: 1fr;
gap: 10px;
}
.info-item {
flex-direction: column;
align-items: flex-start;
}
.info-item .label {
width: auto;
margin-bottom: 3px;
font-size: 12px;
color: #909399;
}
.info-item .value {
font-size: 14px;
}
.pagination-wrapper {
justify-content: center;
}
.section-header {
flex-direction: column;
gap: 10px;
align-items: flex-start;
}
}
</style>

View File

@ -6,22 +6,24 @@
<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-select v-model="searchForm.apartmentId" placeholder="请选择公寓" style="width: 100%">
<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-select v-model="searchForm.status" placeholder="请选择主状态" style="width: 100%">
<el-option label="全部" value=""></el-option>
<el-option label="空房" value="empty"></el-option>
<el-option label="在租" value="rented"></el-option>
</el-select>
</el-form-item>
<el-form-item label="附属状态">
<el-select v-model="searchForm.subStatus" placeholder="请选择附属状态">
<el-select v-model="searchForm.subStatus" placeholder="请选择附属状态" style="width: 100%">
<el-option label="全部" value=""></el-option>
<el-option label="正常" value="normal"></el-option>
<el-option label="即将到期" value="soon_expire"></el-option>
@ -29,7 +31,7 @@
</el-select>
</el-form-item>
<el-form-item label="其他状态">
<el-select v-model="searchForm.otherStatus" placeholder="请选择其他状态">
<el-select v-model="searchForm.otherStatus" placeholder="请选择其他状态" style="width: 100%">
<el-option label="全部" value=""></el-option>
<el-option label="打扫中" value="cleaning"></el-option>
<el-option label="维修中" value="maintenance"></el-option>
@ -43,47 +45,50 @@
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<div class="room-cards">
<!-- 房间卡片列表 -->
<div class="room-cards" v-loading="isLoading">
<div
v-for="room in rooms"
:key="room.id"
@click="handleRoomClick(room.id)"
style="cursor: pointer;"
class="room-card-wrapper"
>
<el-card
:class="['room-card', `status-${room.status === 'rented' ? room.subStatus : room.status}`]"
>
<div class="room-card-header">
<h3>{{ getApartmentName(room.apartmentId) }}</h3>
<div style="display: flex; gap: 8px;">
<el-tag v-if="room.status === 'rented'" :type="getSubStatusType(room.subStatus)">{{ getSubStatusText(room.subStatus) }}</el-tag>
<el-tag v-else>空房</el-tag>
<el-tag v-if="room.otherStatus" :type="getOtherStatusType(room.otherStatus)">{{ getOtherStatusText(room.otherStatus) }}</el-tag>
</div>
</div>
<div class="room-card-body">
<div class="room-info">
<span class="room-number">{{ room.roomNumber }}</span>
<span class="room-area">{{ room.area }}</span>
<div class="price-info">
<span class="room-price">¥{{ room.monthlyPrice }}/</span>
<span v-if="room.yearlyPrice" class="room-price yearly">¥{{ room.yearlyPrice }}/</span>
<div class="room-card-header">
<h3>{{ getApartmentName(room.apartmentId) }}</h3>
<div class="room-card-tags">
<el-tag v-if="room.status === 'rented'" :type="getSubStatusType(room.subStatus)" size="small">{{ getSubStatusText(room.subStatus) }}</el-tag>
<el-tag v-else size="small">空房</el-tag>
<el-tag v-if="room.otherStatus" :type="getOtherStatusType(room.otherStatus)" size="small">{{ getOtherStatusText(room.otherStatus) }}</el-tag>
</div>
</div>
<div class="rental-info" v-if="room.Rentals && room.Rentals.length > 0">
<p>租客: {{ room.Rentals[0].tenantName }}</p>
<p>租期: {{ room.Rentals[0].startDate }} {{ room.Rentals[0].endDate }}</p>
<div class="room-card-body">
<div class="room-info">
<span class="room-number">{{ room.roomNumber }}</span>
<span class="room-area">{{ room.area }}</span>
<div class="price-info">
<span class="room-price">¥{{ room.monthlyPrice }}/</span>
<span v-if="room.yearlyPrice" class="room-price yearly">¥{{ room.yearlyPrice }}/</span>
</div>
</div>
<div class="rental-info" v-if="room.Rentals && room.Rentals.length > 0">
<p><i class="el-icon-user"></i> {{ room.Rentals[0].tenantName }}</p>
<p><i class="el-icon-date"></i> {{ room.Rentals[0].startDate }} {{ room.Rentals[0].endDate }}</p>
</div>
<div class="rental-info" v-else>
<p class="no-rental">暂无租客信息</p>
</div>
</div>
<div class="rental-info" v-else>
<p>暂无租客信息</p>
</div>
</div>
</el-card>
</div>
</div>
<div class="pagination" style="margin-top: 20px;">
<div class="pagination-wrapper">
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:layout="paginationLayout"
:total="total"
:page-size="pageSize"
:current-page="currentPage"
@ -115,26 +120,36 @@ export default {
},
currentPage: 1,
pageSize: 50,
isLoading: false
isLoading: false,
isMobile: false
}
},
computed: {
paginationLayout() {
return this.isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'
}
},
mounted() {
//
this.checkDevice()
window.addEventListener('resize', this.checkDevice)
this.initFromQuery()
this.loadData()
},
beforeDestroy() {
window.removeEventListener('resize', this.checkDevice)
},
methods: {
//
checkDevice() {
this.isMobile = window.innerWidth <= 768
},
initFromQuery() {
const query = this.$route.query
if (query) {
//
if (query.apartmentId) this.searchForm.apartmentId = parseInt(query.apartmentId)
if (query.status) this.searchForm.status = query.status
if (query.subStatus) this.searchForm.subStatus = query.subStatus
if (query.otherStatus) this.searchForm.otherStatus = query.otherStatus
if (query.roomNumber) this.searchForm.roomNumber = query.roomNumber
//
if (query.page) this.currentPage = parseInt(query.page)
if (query.pageSize) this.pageSize = parseInt(query.pageSize)
}
@ -144,11 +159,9 @@ export default {
this.isLoading = true
try {
//
const apartmentsResponse = await apartmentApi.getAll()
this.apartments = apartmentsResponse.data || apartmentsResponse
//
const params = {
apartmentId: this.searchForm.apartmentId,
status: this.searchForm.status,
@ -159,13 +172,11 @@ export default {
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
}
@ -197,7 +208,7 @@ export default {
},
getOtherStatusType(status) {
switch (status) {
case 'cleaning': return 'info'
case 'cleaning': return 'warning'
case 'maintenance': return 'danger'
default: return ''
}
@ -209,13 +220,8 @@ export default {
default: return status
}
},
handleAdd() {
this.$router.push('/rental/add')
},
handleRoomClick(roomId) {
try {
//
this.$router.push({
path: `/rental/detail/${roomId}`,
query: {
@ -245,7 +251,6 @@ export default {
},
handleCurrentChange(val) {
this.currentPage = val
//
this.$router.push({
path: this.$route.path,
query: {
@ -259,7 +264,6 @@ export default {
handleSizeChange(val) {
this.pageSize = val
this.currentPage = 1
//
this.$router.push({
path: this.$route.path,
query: {
@ -276,7 +280,7 @@ export default {
<style scoped>
.rental-list {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -296,19 +300,14 @@ export default {
margin-top: 20px;
}
.room-card {
.room-card-wrapper {
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 {
transition: all 0.3s ease;
border: 2px solid #e4e7ed;
height: 100%;
}
.room-card:hover {
@ -319,20 +318,25 @@ export default {
.room-card-header {
display: flex;
justify-content: space-between;
align-items: center;
align-items: flex-start;
margin-bottom: 15px;
flex-wrap: wrap;
gap: 8px;
}
.room-card-header {
.room-card-header h3 {
margin: 0;
font-size: 16px;
color: #303133;
}
.room-card-tags {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
flex-shrink: 0;
gap: 5px;
flex-wrap: wrap;
}
.room-card-body {
flex: 1;
display: flex;
flex-direction: column;
}
@ -344,16 +348,19 @@ export default {
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #e4e7ed;
flex-shrink: 0;
flex-wrap: wrap;
gap: 8px;
}
.room-number {
font-size: 24px;
font-size: 20px;
font-weight: bold;
color: #303133;
}
.room-area {
color: #606266;
font-size: 14px;
}
.price-info {
@ -363,8 +370,9 @@ export default {
}
.room-price {
color: #409EFF;
color: #f56c6c;
font-weight: 500;
font-size: 14px;
}
.room-price.yearly {
@ -375,21 +383,32 @@ export default {
.rental-info {
margin-top: 10px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
.rental-info p {
margin: 5px 0;
color: #606266;
font-size: 13px;
display: flex;
align-items: center;
gap: 5px;
}
.rental-info p.no-rental {
color: #909399;
font-style: italic;
}
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
/* 状态样式 */
.status-empty {
border-color: #ffffff;
background-color: #f9f9f9;
border-color: #dcdfe6;
background-color: #f5f7fa;
}
.status-normal {
@ -406,4 +425,79 @@ export default {
border-color: #f56c6c;
background-color: #fef0f0;
}
</style>
/* 移动端适配 */
@media screen and (max-width: 768px) {
.search-form {
margin-bottom: 15px;
}
.search-form .el-form-item {
display: block;
margin-right: 0;
margin-bottom: 10px;
}
.search-form .el-form-item__content {
width: 100%;
}
.search-form .el-input,
.search-form .el-select {
width: 100%;
}
.room-cards {
grid-template-columns: 1fr;
gap: 10px;
margin-top: 15px;
}
.room-card {
padding: 12px;
}
.room-card:hover {
transform: none;
}
.room-card-header h3 {
font-size: 14px;
}
.room-number {
font-size: 16px;
}
.room-info {
margin-bottom: 10px;
padding-bottom: 10px;
}
.pagination-wrapper {
justify-content: center;
}
.card-header span {
font-size: 16px;
}
}
@media screen and (max-width: 375px) {
.room-cards {
gap: 8px;
}
.room-card {
padding: 10px;
}
.room-number {
font-size: 14px;
}
.room-price {
font-size: 13px;
}
}
</style>

View File

@ -51,55 +51,117 @@
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="rentalList" style="width: 100%" v-loading="isLoading">
<el-table-column prop="tenantName" label="租客" width="120"></el-table-column>
<el-table-column label="房间" width="150">
<template slot-scope="scope">
{{ getApartmentName(scope.row.roomId) }} - {{ getRoomNumber(scope.row.roomId) }}
</template>
</el-table-column>
<el-table-column prop="startDate" label="开始日期" width="120"></el-table-column>
<el-table-column prop="endDate" label="结束日期" width="120"></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' ? '在租' : '已到期' }}
<!-- PC端表格 -->
<div class="table-wrapper hidden-xs-only">
<el-table :data="rentalList" style="width: 100%" v-loading="isLoading">
<el-table-column prop="tenantName" label="租客" min-width="100"></el-table-column>
<el-table-column label="区域" min-width="150">
<template slot-scope="scope">
{{ scope.row.Room.Apartment.Region.name }}
</template>
</el-table-column>
<el-table-column label="公寓" min-width="150">
<template slot-scope="scope">
{{ scope.row.Room.Apartment.name }}
</template>
</el-table-column>
<el-table-column label="房间" min-width="150">
<template slot-scope="scope">
{{ scope.row.Room.roomNumber}}
</template>
</el-table-column>
<el-table-column prop="startDate" label="开始日期" min-width="100"></el-table-column>
<el-table-column prop="endDate" label="结束日期" min-width="100"></el-table-column>
<el-table-column prop="rent" label="租金(元/月)" min-width="100"></el-table-column>
<el-table-column prop="deposit" label="押金(元)" min-width="100"></el-table-column>
<el-table-column prop="remark" label="备注" min-width="120"></el-table-column>
<el-table-column prop="status" label="状态" min-width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 'active' ? 'success' : 'danger'" size="small">
{{ scope.row.status === 'active' ? '在租' : '已到期' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="150"></el-table-column>
<el-table-column label="操作" min-width="200" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="success" size="mini" @click="handleRenew(scope.row)">续租</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 移动端卡片列表 -->
<div class="mobile-list hidden-sm-and-up">
<div v-for="item in rentalList" :key="item.id" class="mobile-card">
<div class="mobile-card-header">
<span class="mobile-card-title">{{ item.tenantName }}</span>
<el-tag :type="item.status === 'active' ? 'success' : 'danger'" size="mini">
{{ item.status === 'active' ? '在租' : '已到期' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
<el-table-column label="操作" width="220">
<template slot-scope="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="success" size="small" @click="handleRenew(scope.row)">续租</el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
</div>
<div class="mobile-card-body">
<div class="mobile-card-item">
<span class="mobile-card-label">区域</span>
<span class="mobile-card-value">{{ item.Room.Apartment.Region.name }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">公寓</span>
<span class="mobile-card-value">{{ item.Room.Apartment.name }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">房间</span>
<span class="mobile-card-value">{{ item.Room.roomNumber }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">租期</span>
<span class="mobile-card-value">{{ item.startDate }} {{ item.endDate }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">租金</span>
<span class="mobile-card-value">¥{{ item.rent }}/</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">押金</span>
<span class="mobile-card-value">¥{{ item.deposit }}</span>
</div>
<div class="mobile-card-item" v-if="item.remark">
<span class="mobile-card-label">备注</span>
<span class="mobile-card-value">{{ item.remark }}</span>
</div>
</div>
<div class="mobile-card-footer">
<el-button type="primary" size="mini" @click="handleEdit(item)">编辑</el-button>
<el-button type="success" size="mini" @click="handleRenew(item)">续租</el-button>
<el-button type="danger" size="mini" @click="handleDelete(item.id)">删除</el-button>
</div>
</div>
</div>
<div class="pagination-wrapper">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:layout="isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
:total="total">
</el-pagination>
</div>
</el-card>
<el-dialog :title="rentalForm.id ? '编辑租赁记录' : '新增租赁记录'" :visible.sync="rentalDialogVisible" width="500px">
<el-form :model="rentalForm" :rules="rentalRules" ref="rentalForm">
<el-form :model="rentalForm" :rules="rentalRules" ref="rentalForm" label-width="80px">
<el-form-item label="租客姓名" prop="tenantName">
<el-input v-model="rentalForm.tenantName" placeholder="请输入租客姓名"></el-input>
</el-form-item>
<el-form-item label="房间" prop="roomId">
<el-select v-model="rentalForm.roomId" placeholder="请选择房间" style="width: 100%">
<el-option v-for="room in allRooms" :key="room.id" :label="`${getApartmentName(room.id)} - ${room.roomNumber}`" :value="room.id"></el-option>
<el-option v-for="room in allRooms" :key="room.id" :label="`${room.Apartment.name} - ${room.roomNumber}`" :value="room.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="开始日期" prop="startDate">
@ -176,15 +238,27 @@ export default {
}
}
},
computed: {
isMobile() {
return window.innerWidth <= 768
}
},
mounted() {
this.loadApartments()
this.loadAllRooms()
this.loadData()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
},
methods: {
handleResize() {
this.$forceUpdate()
},
async loadApartments() {
try {
const response = await apartmentApi.getAll()
const response = await apartmentApi.list()
this.apartments = response.data || response
} catch (error) {
this.$message.error('加载公寓数据失败')
@ -192,7 +266,7 @@ export default {
},
async loadAllRooms() {
try {
const response = await roomApi.getAll()
const response = await roomApi.list()
this.allRooms = response.data || response
} catch (error) {
this.$message.error('加载房间数据失败')
@ -204,7 +278,7 @@ export default {
return
}
try {
const response = await roomApi.getAll({ apartmentId: this.searchForm.apartmentId })
const response = await roomApi.list({ apartmentId: this.searchForm.apartmentId })
this.rooms = response.data || response
} catch (error) {
this.$message.error('加载房间数据失败')
@ -353,7 +427,7 @@ export default {
<style scoped>
.rental-archive {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -366,9 +440,31 @@ export default {
margin-bottom: 20px;
}
.pagination {
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.search-form .el-form-item {
display: block;
margin-right: 0;
margin-bottom: 10px;
}
.search-form .el-form-item__content {
width: 100%;
}
.search-form .el-select,
.search-form .el-date-picker {
width: 100%;
}
.pagination-wrapper {
justify-content: center;
}
}
</style>

View File

@ -42,49 +42,96 @@
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="waterBillList" style="width: 100%" v-loading="isLoading">
<el-table-column label="房间" width="150">
<template slot-scope="scope">
{{ getApartmentName(scope.row.roomId) }} - {{ getRoomNumber(scope.row.roomId) }}
</template>
</el-table-column>
<el-table-column prop="startDate" label="开始日期" width="120"></el-table-column>
<el-table-column prop="endDate" label="结束日期" width="120"></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' ? '已支付' : '未支付' }}
<!-- PC端表格 -->
<div class="table-wrapper hidden-xs-only">
<el-table :data="waterBillList" style="width: 100%" v-loading="isLoading">
<el-table-column label="房间" min-width="150">
<template slot-scope="scope">
{{ getApartmentName(scope.row.roomId) }} - {{ getRoomNumber(scope.row.roomId) }}
</template>
</el-table-column>
<el-table-column prop="startDate" label="开始日期" min-width="100"></el-table-column>
<el-table-column prop="endDate" label="结束日期" min-width="100"></el-table-column>
<el-table-column prop="startReading" label="起始度数" min-width="90"></el-table-column>
<el-table-column prop="endReading" label="结束度数" min-width="90"></el-table-column>
<el-table-column prop="usage" label="用水量(吨)" min-width="100"></el-table-column>
<el-table-column prop="unitPrice" label="单价(元/吨)" min-width="100"></el-table-column>
<el-table-column prop="amount" label="费用(元)" min-width="90">
<template slot-scope="scope">
<span class="amount-value">¥{{ scope.row.amount }}</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" min-width="80">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 'paid' ? 'success' : 'warning'" size="small">
{{ scope.row.status === 'paid' ? '已支付' : '未支付' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="150"></el-table-column>
<el-table-column label="操作" min-width="150" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="mini" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 移动端卡片列表 -->
<div class="mobile-list hidden-sm-and-up">
<div v-for="item in waterBillList" :key="item.id" class="mobile-card">
<div class="mobile-card-header">
<span class="mobile-card-title">{{ getApartmentName(item.roomId) }} - {{ getRoomNumber(item.roomId) }}</span>
<el-tag :type="item.status === 'paid' ? 'success' : 'warning'" size="mini">
{{ item.status === 'paid' ? '已支付' : '未支付' }}
</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 type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination">
</div>
<div class="mobile-card-body">
<div class="mobile-card-item">
<span class="mobile-card-label">计费周期</span>
<span class="mobile-card-value">{{ item.startDate }} {{ item.endDate }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">用水量</span>
<span class="mobile-card-value">{{ item.usage }} </span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">费用</span>
<span class="mobile-card-value amount-value">¥{{ item.amount }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">读数</span>
<span class="mobile-card-value">{{ item.startReading }} {{ item.endReading }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">单价</span>
<span class="mobile-card-value">¥{{ item.unitPrice }}/</span>
</div>
</div>
<div class="mobile-card-footer">
<el-button type="primary" size="mini" @click="handleEdit(item)">编辑</el-button>
<el-button type="danger" size="mini" @click="handleDelete(item.id)">删除</el-button>
</div>
</div>
</div>
<div class="pagination-wrapper">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:layout="isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
:total="total">
</el-pagination>
</div>
</el-card>
<el-dialog :title="waterBillForm.id ? '编辑水费' : '添加水费'" :visible.sync="waterBillDialogVisible" width="500px">
<el-form :model="waterBillForm" :rules="waterBillRules" ref="waterBillForm">
<el-form :model="waterBillForm" :rules="waterBillRules" ref="waterBillForm" label-width="90px">
<el-form-item label="房间" prop="roomId">
<el-select v-model="waterBillForm.roomId" placeholder="请选择房间" style="width: 100%">
<el-option v-for="room in allRooms" :key="room.id" :label="`${getApartmentName(room.id)} - ${room.roomNumber}`" :value="room.id"></el-option>
@ -163,15 +210,27 @@ export default {
}
}
},
computed: {
isMobile() {
return window.innerWidth <= 768
}
},
mounted() {
this.loadApartments()
this.loadAllRooms()
this.loadData()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
},
methods: {
handleResize() {
this.$forceUpdate()
},
async loadApartments() {
try {
const response = await apartmentApi.getAll()
const response = await apartmentApi.list()
this.apartments = response.data || response
} catch (error) {
this.$message.error('加载公寓数据失败')
@ -179,7 +238,7 @@ export default {
},
async loadAllRooms() {
try {
const response = await roomApi.getAll()
const response = await roomApi.list()
this.allRooms = response.data || response
} catch (error) {
this.$message.error('加载房间数据失败')
@ -191,7 +250,7 @@ export default {
return
}
try {
const response = await roomApi.getAll({ apartmentId: this.searchForm.apartmentId })
const response = await roomApi.list({ apartmentId: this.searchForm.apartmentId })
this.rooms = response.data || response
} catch (error) {
this.$message.error('加载房间数据失败')
@ -325,7 +384,7 @@ export default {
<style scoped>
.water-archive {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -338,9 +397,36 @@ export default {
margin-bottom: 20px;
}
.pagination {
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.amount-value {
color: #f56c6c;
font-weight: bold;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.search-form .el-form-item {
display: block;
margin-right: 0;
margin-bottom: 10px;
}
.search-form .el-form-item__content {
width: 100%;
}
.search-form .el-select,
.search-form .el-date-picker {
width: 100%;
}
.pagination-wrapper {
justify-content: center;
}
}
</style>

View File

@ -6,9 +6,9 @@
<span>添加房间</span>
</div>
</template>
<el-form :model="roomForm" :rules="rules" ref="roomForm" label-width="100px">
<el-form :model="roomForm" :rules="rules" ref="roomForm" label-width="100px" class="form-content">
<el-form-item label="公寓" prop="apartmentId">
<el-select v-model="roomForm.apartmentId" placeholder="请选择公寓">
<el-select v-model="roomForm.apartmentId" placeholder="请选择公寓" style="width: 100%">
<el-option v-for="apartment in apartments" :key="apartment.id" :label="apartment.name" :value="apartment.id"></el-option>
</el-select>
</el-form-item>
@ -25,14 +25,14 @@
<el-input type="number" v-model="roomForm.yearlyPrice" placeholder="请输入年租金"></el-input>
</el-form-item>
<el-form-item label="主状态" prop="status">
<el-select v-model="roomForm.status" placeholder="请选择主状态">
<el-select v-model="roomForm.status" placeholder="请选择主状态" style="width: 100%">
<el-option label="空房" value="empty"></el-option>
<el-option label="在租" value="rented"></el-option>
</el-select>
</el-form-item>
<el-form-item label="附属状态" prop="subStatus" v-if="roomForm.status === 'rented'">
<el-select v-model="roomForm.subStatus" placeholder="请选择附属状态">
<el-select v-model="roomForm.subStatus" placeholder="请选择附属状态" style="width: 100%">
<el-option label="正常" value="normal"></el-option>
<el-option label="即将到期" value="soon_expire"></el-option>
<el-option label="已到期" value="expired"></el-option>
@ -40,13 +40,13 @@
</el-form-item>
<el-form-item label="其他状态" prop="otherStatus">
<el-select v-model="roomForm.otherStatus" placeholder="请选择其他状态">
<el-select v-model="roomForm.otherStatus" placeholder="请选择其他状态" style="width: 100%">
<el-option label="无" value=""></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-form-item class="form-actions">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
<el-button @click="goBack">返回</el-button>
@ -160,7 +160,7 @@ export default {
<style scoped>
.room-add {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -168,4 +168,38 @@ export default {
justify-content: space-between;
align-items: center;
}
</style>
.form-content {
max-width: 600px;
}
.form-actions {
margin-top: 30px;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.form-content {
max-width: 100%;
}
.form-content .el-form-item__label {
float: none;
display: block;
text-align: left;
margin-bottom: 5px;
}
.form-content .el-form-item__content {
margin-left: 0 !important;
}
.form-actions {
text-align: center;
}
.form-actions .el-button {
margin-bottom: 10px;
}
}
</style>

View File

@ -6,9 +6,9 @@
<span>编辑房间</span>
</div>
</template>
<el-form :model="roomForm" :rules="rules" ref="roomForm" label-width="100px">
<el-form :model="roomForm" :rules="rules" ref="roomForm" label-width="100px" class="form-content">
<el-form-item label="公寓" prop="apartmentId">
<el-select v-model="roomForm.apartmentId" placeholder="请选择公寓">
<el-select v-model="roomForm.apartmentId" placeholder="请选择公寓" style="width: 100%">
<el-option v-for="apartment in apartments" :key="apartment.id" :label="apartment.name" :value="apartment.id"></el-option>
</el-select>
</el-form-item>
@ -25,14 +25,14 @@
<el-input type="number" v-model="roomForm.yearlyPrice" placeholder="请输入年租金"></el-input>
</el-form-item>
<el-form-item label="主状态" prop="status">
<el-select v-model="roomForm.status" placeholder="请选择主状态">
<el-select v-model="roomForm.status" placeholder="请选择主状态" style="width: 100%">
<el-option label="空房" value="empty"></el-option>
<el-option label="在租" value="rented"></el-option>
</el-select>
</el-form-item>
<el-form-item label="附属状态" prop="subStatus" v-if="roomForm.status === 'rented'">
<el-select v-model="roomForm.subStatus" placeholder="请选择附属状态">
<el-select v-model="roomForm.subStatus" placeholder="请选择附属状态" style="width: 100%">
<el-option label="正常" value="normal"></el-option>
<el-option label="即将到期" value="soon_expire"></el-option>
<el-option label="已到期" value="expired"></el-option>
@ -40,13 +40,13 @@
</el-form-item>
<el-form-item label="其他状态" prop="otherStatus">
<el-select v-model="roomForm.otherStatus" placeholder="请选择其他状态">
<el-select v-model="roomForm.otherStatus" placeholder="请选择其他状态" style="width: 100%">
<el-option label="无" value=""></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-form-item class="form-actions">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
<el-button @click="goBack">返回</el-button>
@ -74,7 +74,6 @@ export default {
otherStatus: '',
subStatus: 'normal'
},
//
returnQuery: {},
apartments: [],
rules: {
@ -125,7 +124,6 @@ export default {
}
},
mounted() {
//
this.returnQuery = this.$route.query
this.loadApartments()
this.loadRoomData()
@ -183,7 +181,7 @@ export default {
<style scoped>
.room-edit {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -191,4 +189,38 @@ export default {
justify-content: space-between;
align-items: center;
}
</style>
.form-content {
max-width: 600px;
}
.form-actions {
margin-top: 30px;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.form-content {
max-width: 100%;
}
.form-content .el-form-item__label {
float: none;
display: block;
text-align: left;
margin-bottom: 5px;
}
.form-content .el-form-item__content {
margin-left: 0 !important;
}
.form-actions {
text-align: center;
}
.form-actions .el-button {
margin-bottom: 10px;
}
}
</style>

View File

@ -4,25 +4,27 @@
<template slot="header">
<div class="card-header">
<span>房间管理</span>
<el-button type="primary" @click="handleAdd">添加房间</el-button>
<el-button type="primary" size="small" @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-select v-model="searchForm.apartmentId" placeholder="请选择公寓" style="width: 100%">
<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-select v-model="searchForm.status" placeholder="请选择主状态" style="width: 100%">
<el-option label="全部" value=""></el-option>
<el-option label="空房" value="empty"></el-option>
<el-option label="在租" value="rented"></el-option>
</el-select>
</el-form-item>
<el-form-item label="附属状态">
<el-select v-model="searchForm.subStatus" placeholder="请选择附属状态">
<el-select v-model="searchForm.subStatus" placeholder="请选择附属状态" style="width: 100%">
<el-option label="全部" value=""></el-option>
<el-option label="正常" value="normal"></el-option>
<el-option label="即将到期" value="soon_expire"></el-option>
@ -30,7 +32,7 @@
</el-select>
</el-form-item>
<el-form-item label="其他状态">
<el-select v-model="searchForm.otherStatus" placeholder="请选择其他状态">
<el-select v-model="searchForm.otherStatus" placeholder="请选择其他状态" style="width: 100%">
<el-option label="全部" value=""></el-option>
<el-option label="打扫中" value="cleaning"></el-option>
<el-option label="维修中" value="maintenance"></el-option>
@ -44,45 +46,94 @@
<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="monthlyPrice" label="月租金(元)" width="120"></el-table-column>
<el-table-column prop="yearlyPrice" 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="subStatus" label="附属状态" width="120">
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 'rented'" :type="getSubStatusType(scope.row.subStatus)">{{ getSubStatusText(scope.row.subStatus) }}</el-tag>
<span v-else></span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
<el-table-column prop="otherStatus" label="其他状态" width="120">
<template slot-scope="scope">
<el-tag v-if="scope.row.otherStatus" :type="getOtherStatusType(scope.row.otherStatus)">{{ getOtherStatusText(scope.row.otherStatus) }}</el-tag>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<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;">
<!-- PC端表格 -->
<div class="table-wrapper hidden-xs-only">
<el-table
:data="rooms"
style="width: 100%"
v-loading="isLoading"
>
<el-table-column prop="apartmentName" label="公寓" min-width="120"></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="monthlyPrice" label="月租金(元)" width="120"></el-table-column>
<el-table-column prop="yearlyPrice" 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)" size="small">{{ getStatusText(scope.row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="subStatus" label="附属状态" width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.status === 'rented'" :type="getSubStatusType(scope.row.subStatus)" size="small">{{ getSubStatusText(scope.row.subStatus) }}</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="otherStatus" label="其他状态" width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.otherStatus" :type="getOtherStatusType(scope.row.otherStatus)" size="small">{{ getOtherStatusText(scope.row.otherStatus) }}</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="handleEdit(scope.row.id)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 移动端卡片列表 -->
<div class="mobile-list hidden-sm-and-up">
<div v-for="item in rooms" :key="item.id" class="mobile-card">
<div class="mobile-card-header">
<div class="mobile-card-title">
<span>{{ item.apartmentName }}</span>
<span class="room-number">{{ item.roomNumber }}</span>
</div>
<div class="mobile-card-tags">
<el-tag :type="getStatusType(item.status)" size="mini">{{ getStatusText(item.status) }}</el-tag>
<el-tag v-if="item.status === 'rented' && item.subStatus" :type="getSubStatusType(item.subStatus)" size="mini">
{{ getSubStatusText(item.subStatus) }}
</el-tag>
<el-tag v-if="item.otherStatus" :type="getOtherStatusType(item.otherStatus)" size="mini">
{{ getOtherStatusText(item.otherStatus) }}
</el-tag>
</div>
</div>
<div class="mobile-card-body">
<div class="mobile-card-row">
<div class="mobile-card-item">
<span class="mobile-card-label">面积:</span>
<span class="mobile-card-value">{{ item.area }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">月租:</span>
<span class="mobile-card-value price">¥{{ item.monthlyPrice }}</span>
</div>
</div>
<div class="mobile-card-item" v-if="item.yearlyPrice">
<span class="mobile-card-label">年租:</span>
<span class="mobile-card-value price">¥{{ item.yearlyPrice }}</span>
</div>
<div class="mobile-card-item">
<span class="mobile-card-label">创建时间:</span>
<span class="mobile-card-value">{{ item.createTime }}</span>
</div>
</div>
<div class="mobile-card-footer">
<el-button size="mini" type="primary" @click="handleEdit(item.id)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(item.id)">删除</el-button>
</div>
</div>
</div>
<div class="pagination-wrapper">
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
:layout="paginationLayout"
:total="total"
:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
@ -114,26 +165,36 @@ export default {
},
currentPage: 1,
pageSize: 10,
isLoading: false
isLoading: false,
isMobile: false
}
},
computed: {
paginationLayout() {
return this.isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'
}
},
mounted() {
//
this.checkDevice()
window.addEventListener('resize', this.checkDevice)
this.initFromQuery()
this.loadData()
},
beforeDestroy() {
window.removeEventListener('resize', this.checkDevice)
},
methods: {
//
checkDevice() {
this.isMobile = window.innerWidth <= 768
},
initFromQuery() {
const query = this.$route.query
if (query) {
//
if (query.apartmentId) this.searchForm.apartmentId = parseInt(query.apartmentId)
if (query.status) this.searchForm.status = query.status
if (query.subStatus) this.searchForm.subStatus = query.subStatus
if (query.otherStatus) this.searchForm.otherStatus = query.otherStatus
if (query.roomNumber) this.searchForm.roomNumber = query.roomNumber
//
if (query.page) this.currentPage = parseInt(query.page)
if (query.pageSize) this.pageSize = parseInt(query.pageSize)
}
@ -143,11 +204,9 @@ export default {
this.isLoading = true
try {
//
const apartmentsResponse = await apartmentApi.getAll()
this.apartments = apartmentsResponse.data || apartmentsResponse
//
const params = {
apartmentId: this.searchForm.apartmentId,
status: this.searchForm.status,
@ -158,11 +217,9 @@ export default {
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,
@ -171,7 +228,6 @@ export default {
})
this.total = roomsResponse.total
} else {
//
this.rooms = roomsResponse.map(room => {
const apartment = this.apartments.find(a => a.id == room.apartmentId)
return {
@ -189,7 +245,7 @@ export default {
},
getStatusType(status) {
switch (status) {
case 'empty': return ''
case 'empty': return 'info'
case 'rented': return 'success'
default: return ''
}
@ -203,7 +259,7 @@ export default {
},
getOtherStatusType(status) {
switch (status) {
case 'cleaning': return 'info'
case 'cleaning': return 'warning'
case 'maintenance': return 'danger'
default: return ''
}
@ -235,7 +291,6 @@ export default {
this.$router.push('/room/add')
},
handleEdit(id) {
//
this.$router.push({
path: `/room/edit/${id}`,
query: {
@ -292,7 +347,7 @@ export default {
<style scoped>
.room-list {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -305,8 +360,148 @@ export default {
margin-bottom: 20px;
}
.pagination {
.table-wrapper {
overflow-x: auto;
}
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
</style>
/* 移动端卡片列表样式 */
.mobile-list {
display: none;
}
.mobile-card {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 15px;
margin-bottom: 10px;
}
.mobile-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #ebeef5;
flex-wrap: wrap;
gap: 8px;
}
.mobile-card-title {
display: flex;
flex-direction: column;
gap: 4px;
}
.mobile-card-title span:first-child {
font-size: 14px;
color: #606266;
}
.room-number {
font-size: 16px;
font-weight: bold;
color: #303133;
}
.mobile-card-tags {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.mobile-card-body {
margin-bottom: 10px;
}
.mobile-card-row {
display: flex;
gap: 20px;
}
.mobile-card-item {
display: flex;
margin-bottom: 8px;
font-size: 14px;
}
.mobile-card-label {
color: #909399;
min-width: 50px;
flex-shrink: 0;
}
.mobile-card-value {
color: #606266;
flex: 1;
}
.mobile-card-value.price {
color: #f56c6c;
font-weight: bold;
}
.mobile-card-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding-top: 10px;
border-top: 1px solid #ebeef5;
}
/* 响应式显示控制 */
@media screen and (max-width: 768px) {
.hidden-xs-only {
display: none;
}
.mobile-list {
display: block;
}
.search-form {
margin-bottom: 15px;
}
.search-form .el-form-item {
display: block;
margin-right: 0;
margin-bottom: 10px;
}
.search-form .el-form-item__content {
width: 100%;
}
.search-form .el-input,
.search-form .el-select {
width: 100%;
}
.pagination-wrapper {
justify-content: center;
}
.card-header {
flex-wrap: wrap;
gap: 10px;
}
.card-header span {
font-size: 16px;
}
}
@media screen and (min-width: 769px) {
.hidden-sm-and-up {
display: none;
}
}
</style>

View File

@ -9,13 +9,23 @@
<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 class="table-wrapper">
<el-table :data="roomStatusData" style="width: 100%; margin-top: 20px;" class="stats-table">
<el-table-column prop="status" label="状态" min-width="100"></el-table-column>
<el-table-column prop="count" label="数量" min-width="80">
<template slot-scope="scope">
<span class="count-value">{{ scope.row.count }}</span>
</template>
</el-table-column>
<el-table-column prop="percentage" label="占比" min-width="100">
<template slot-scope="scope">
<el-progress :percentage="parseFloat(scope.row.percentage)" :show-text="true"></el-progress>
</template>
</el-table-column>
</el-table>
</div>
<div class="total-count">
<el-tag size="medium" type="primary">房间总数{{ totalCount }} </el-tag>
</div>
</el-card>
</div>
@ -33,7 +43,6 @@ export default {
},
computed: {
totalCount() {
//
return this.roomStatusData
.filter(item => item.status === '在租' || item.status === '空房')
.reduce((sum, item) => sum + item.count, 0)
@ -47,13 +56,12 @@ export default {
try {
const response = await statisticsApi.getRoomStatus()
const data = response
//
const total = data
.filter(item => item.status === '在租' || item.status === '空房')
.reduce((sum, item) => sum + item.count, 0)
this.roomStatusData = data.map(item => ({
...item,
percentage: total > 0 ? `${((item.count / total) * 100).toFixed(2)}%` : '0.00%'
percentage: total > 0 ? parseFloat(((item.count / total) * 100).toFixed(2)) : 0
}))
} catch (error) {
this.$message.error('加载房间状态统计数据失败')
@ -65,7 +73,7 @@ export default {
<style scoped>
.room-statistics {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -79,4 +87,38 @@ export default {
display: flex;
justify-content: center;
}
</style>
.table-wrapper {
overflow-x: auto;
}
.stats-table {
min-width: 350px;
}
.count-value {
font-weight: bold;
color: #409EFF;
}
.total-count {
margin-top: 20px;
text-align: right;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.chart-container {
margin: 10px 0;
}
.total-count {
text-align: center;
margin-top: 15px;
}
.card-header span {
font-size: 16px;
}
}
</style>

View File

@ -9,12 +9,18 @@
<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 class="table-wrapper">
<el-table :data="rentData" style="width: 100%; margin-top: 20px;" class="stats-table">
<el-table-column prop="month" label="月份" min-width="100"></el-table-column>
<el-table-column prop="amount" label="租金收入(元)" min-width="150">
<template slot-scope="scope">
<span class="amount-value">¥{{ scope.row.amount }}</span>
</template>
</el-table-column>
</el-table>
</div>
<div class="total-amount">
<el-tag size="medium" type="primary">总租金收入{{ totalAmount }} </el-tag>
</div>
</el-card>
</div>
@ -53,7 +59,7 @@ export default {
<style scoped>
.rent-statistics {
padding: 20px 0;
padding: 0;
}
.card-header {
@ -65,4 +71,38 @@ export default {
.chart-container {
margin: 20px 0;
}
</style>
.table-wrapper {
overflow-x: auto;
}
.stats-table {
min-width: 300px;
}
.amount-value {
color: #f56c6c;
font-weight: bold;
}
.total-amount {
margin-top: 20px;
text-align: right;
}
/* 移动端适配 */
@media screen and (max-width: 768px) {
.chart-container {
margin: 10px 0;
}
.total-amount {
text-align: center;
margin-top: 15px;
}
.card-header span {
font-size: 16px;
}
}
</style>