租房日历模块
This commit is contained in:
parent
74ec7491f6
commit
18a7febec9
|
|
@ -36,7 +36,7 @@
|
|||
<el-card v-loading="loading" style="margin-top:20px">
|
||||
<el-calendar v-model="currentDate">
|
||||
<template slot="dateCell" slot-scope="{ date, data }">
|
||||
<div class="calendar-cell" :class="{ 'is-current-month': data.type === 'current-month' }">
|
||||
<div class="calendar-cell" :class="{ 'is-current-month': data.type === 'current-month' }" @click="data.type === 'current-month' && showDayDetail(data.day)">
|
||||
<div class="cell-day">{{ data.day.split('-').pop() }}</div>
|
||||
<div class="cell-items" v-if="getItemsForDate(data.day).length > 0">
|
||||
<div
|
||||
|
|
@ -45,11 +45,10 @@
|
|||
:key="item.id"
|
||||
:class="item.statusClass"
|
||||
:title="item.tenantName + '(' + item.apartmentName + '-' + item.roomNumber + ')'"
|
||||
@click="goToDetail(item.roomId)"
|
||||
>
|
||||
{{ item.tenantName }}({{ item.apartmentName }}-{{ item.roomNumber }})
|
||||
</div>
|
||||
<div class="cell-more" v-if="getItemsForDate(data.day).length > 3" @click="showDayDetail(data.day)">
|
||||
<div class="cell-more" v-if="getItemsForDate(data.day).length > 3">
|
||||
+{{ getItemsForDate(data.day).length - 3 }} 更多
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -59,29 +58,29 @@
|
|||
</el-card>
|
||||
|
||||
<el-dialog :visible.sync="dayDialogVisible" :title="formatSelectedDate + ' —— (' + selectedDayItems.length + ' 条)'" width="70%" @close="selectedDate = null">
|
||||
<el-table :data="selectedDayItems" stripe style="width: 100%">
|
||||
<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="tenantName" label="租客姓名" min-width="120"></el-table-column>
|
||||
<el-table-column prop="rent" label="租金" width="120">
|
||||
<template slot-scope="scope">¥{{ scope.row.rent }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deposit" label="押金" width="120">
|
||||
<template slot-scope="scope">¥{{ scope.row.deposit }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.subStatus === 'expired' ? 'danger' : scope.row.subStatus === 'soon_expire' ? 'warning' : 'success'" size="small">
|
||||
{{ subStatusLabels[scope.row.subStatus] }}
|
||||
<div class="day-card-list">
|
||||
<el-card v-for="item in selectedDayItems" :key="item.id" class="day-card" :class="item.statusClass" shadow="hover" @click.native="goToDetail(item.roomId)">
|
||||
<div class="card-accent"></div>
|
||||
<div class="card-inner">
|
||||
<div class="card-apartment">{{ item.apartmentName }}<span class="card-room"> {{ item.roomNumber }}</span></div>
|
||||
<div class="card-top">
|
||||
<div class="card-tenant">{{ item.tenantName }}</div>
|
||||
<el-tag :type="item.subStatus === 'expired' ? 'danger' : item.subStatus === 'soon_expire' ? 'warning' : 'success'" size="small">
|
||||
{{ subStatusLabels[item.subStatus] }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="goToDetail(scope.row.roomId)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="card-dates">
|
||||
<span class="date-range">{{ item.startDate }} → {{ item.endDate }}</span>
|
||||
</div>
|
||||
<div class="card-amounts">
|
||||
<span class="amount-item">¥{{ item.rent }}/月</span>
|
||||
<span class="amount-divider"></span>
|
||||
<span class="amount-item">押金 ¥{{ item.deposit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-empty v-if="selectedDayItems.length === 0" description="暂无数据"></el-empty>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -101,7 +100,8 @@ export default {
|
|||
apartments: [],
|
||||
filterApartmentId: null,
|
||||
filterTenantName: '',
|
||||
filterSubStatus: ''
|
||||
filterSubStatus: '',
|
||||
_afterLoadCallback: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -122,6 +122,7 @@ export default {
|
|||
tenantName: item.tenantName,
|
||||
roomNumber: item.roomNumber,
|
||||
apartmentName: item.apartmentName,
|
||||
startDate: item.startDate,
|
||||
endDate: item.endDate,
|
||||
status: item.status,
|
||||
subStatus,
|
||||
|
|
@ -156,12 +157,20 @@ export default {
|
|||
mounted() {
|
||||
this.loadApartments()
|
||||
this.loadData()
|
||||
this.$nextTick(() => {
|
||||
this._setupTodayListener()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
this._cleanupTodayListener()
|
||||
},
|
||||
watch: {
|
||||
currentDate(newVal, oldVal) {
|
||||
if (!oldVal || newVal.getMonth() !== oldVal.getMonth() || newVal.getFullYear() !== oldVal.getFullYear()) {
|
||||
if (!this._afterLoadCallback) {
|
||||
this.selectedDate = null
|
||||
this.dayDialogVisible = false
|
||||
}
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
|
|
@ -190,6 +199,10 @@ export default {
|
|||
this.rentals = []
|
||||
} finally {
|
||||
this.loading = false
|
||||
if (this._afterLoadCallback) {
|
||||
this._afterLoadCallback()
|
||||
this._afterLoadCallback = null
|
||||
}
|
||||
}
|
||||
},
|
||||
handleFilterChange() {
|
||||
|
|
@ -216,6 +229,29 @@ export default {
|
|||
},
|
||||
goToDetail(roomId) {
|
||||
this.$router.push(`/rental/detail/${roomId}`)
|
||||
},
|
||||
_setupTodayListener() {
|
||||
const btn = this.$el.querySelector('.el-calendar__button-group .el-button:nth-child(2)')
|
||||
if (!btn) return
|
||||
this._todayBtnHandler = () => {
|
||||
const today = new Date()
|
||||
const ds = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
|
||||
const cur = this.currentDate
|
||||
if (cur.getMonth() === today.getMonth() && cur.getFullYear() === today.getFullYear()) {
|
||||
this.showDayDetail(ds)
|
||||
} else {
|
||||
this._afterLoadCallback = () => {
|
||||
this.selectedDate = ds
|
||||
this.dayDialogVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
btn.addEventListener('click', this._todayBtnHandler)
|
||||
},
|
||||
_cleanupTodayListener() {
|
||||
if (!this._todayBtnHandler) return
|
||||
const btn = this.$el && this.$el.querySelector('.el-calendar__button-group .el-button:nth-child(2)')
|
||||
if (btn) btn.removeEventListener('click', this._todayBtnHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -388,7 +424,7 @@ export default {
|
|||
}
|
||||
|
||||
::v-deep .el-calendar-table td.is-today {
|
||||
background: #f0f5ff;
|
||||
background: #c7d2fe !important;
|
||||
}
|
||||
|
||||
::v-deep .el-calendar-table td.is-today .calendar-cell .cell-day {
|
||||
|
|
@ -397,6 +433,7 @@ export default {
|
|||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
font-weight: 700;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
::v-deep .el-calendar-table td.is-selected {
|
||||
|
|
@ -458,7 +495,7 @@ export default {
|
|||
}
|
||||
|
||||
.cell-item {
|
||||
font-size: 11px;
|
||||
font-size: 13px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
|
@ -570,4 +607,121 @@ export default {
|
|||
::v-deep .el-dialog__body {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.day-card-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.day-card {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.day-card:hover {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.day-card ::v-deep .el-card__body {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.card-accent {
|
||||
width: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.day-card.status-active .card-accent {
|
||||
background: #52c41a;
|
||||
}
|
||||
|
||||
.day-card.status-soon .card-accent {
|
||||
background: #faad14;
|
||||
}
|
||||
|
||||
.day-card.status-expired .card-accent {
|
||||
background: #ff4d4f;
|
||||
}
|
||||
|
||||
.day-card.status-active {
|
||||
background: linear-gradient(135deg, #f0f9eb 0%, #e8f5e0 100%);
|
||||
}
|
||||
|
||||
.day-card.status-soon {
|
||||
background: linear-gradient(135deg, #fff7e6 0%, #fff1cc 100%);
|
||||
}
|
||||
|
||||
.day-card.status-expired {
|
||||
background: linear-gradient(135deg, #fff1f0 0%, #ffe3e0 100%);
|
||||
}
|
||||
|
||||
.card-inner {
|
||||
flex: 1;
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.card-apartment {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1a1a2e;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card-room {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #909399;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.card-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.card-tenant {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.card-dates {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.date-range {
|
||||
font-size: 13px;
|
||||
color: #667eea;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.card-amounts {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 2px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.amount-item {
|
||||
font-size: 12px;
|
||||
color: #bfc4cc;
|
||||
}
|
||||
|
||||
.amount-divider {
|
||||
width: 1px;
|
||||
height: 12px;
|
||||
background: #e0e0e0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue