租房日历模块
This commit is contained in:
parent
74ec7491f6
commit
18a7febec9
|
|
@ -36,7 +36,7 @@
|
||||||
<el-card v-loading="loading" style="margin-top:20px">
|
<el-card v-loading="loading" style="margin-top:20px">
|
||||||
<el-calendar v-model="currentDate">
|
<el-calendar v-model="currentDate">
|
||||||
<template slot="dateCell" slot-scope="{ date, data }">
|
<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-day">{{ data.day.split('-').pop() }}</div>
|
||||||
<div class="cell-items" v-if="getItemsForDate(data.day).length > 0">
|
<div class="cell-items" v-if="getItemsForDate(data.day).length > 0">
|
||||||
<div
|
<div
|
||||||
|
|
@ -45,11 +45,10 @@
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:class="item.statusClass"
|
:class="item.statusClass"
|
||||||
:title="item.tenantName + '(' + item.apartmentName + '-' + item.roomNumber + ')'"
|
:title="item.tenantName + '(' + item.apartmentName + '-' + item.roomNumber + ')'"
|
||||||
@click="goToDetail(item.roomId)"
|
|
||||||
>
|
>
|
||||||
{{ item.tenantName }}({{ item.apartmentName }}-{{ item.roomNumber }})
|
{{ item.tenantName }}({{ item.apartmentName }}-{{ item.roomNumber }})
|
||||||
</div>
|
</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 }} 更多
|
+{{ getItemsForDate(data.day).length - 3 }} 更多
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -59,29 +58,29 @@
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-dialog :visible.sync="dayDialogVisible" :title="formatSelectedDate + ' —— (' + selectedDayItems.length + ' 条)'" width="70%" @close="selectedDate = null">
|
<el-dialog :visible.sync="dayDialogVisible" :title="formatSelectedDate + ' —— (' + selectedDayItems.length + ' 条)'" width="70%" @close="selectedDate = null">
|
||||||
<el-table :data="selectedDayItems" stripe style="width: 100%">
|
<div class="day-card-list">
|
||||||
<el-table-column prop="apartmentName" label="公寓名称" min-width="120"></el-table-column>
|
<el-card v-for="item in selectedDayItems" :key="item.id" class="day-card" :class="item.statusClass" shadow="hover" @click.native="goToDetail(item.roomId)">
|
||||||
<el-table-column prop="roomNumber" label="房间号" width="100"></el-table-column>
|
<div class="card-accent"></div>
|
||||||
<el-table-column prop="tenantName" label="租客姓名" min-width="120"></el-table-column>
|
<div class="card-inner">
|
||||||
<el-table-column prop="rent" label="租金" width="120">
|
<div class="card-apartment">{{ item.apartmentName }}<span class="card-room"> {{ item.roomNumber }}</span></div>
|
||||||
<template slot-scope="scope">¥{{ scope.row.rent }}</template>
|
<div class="card-top">
|
||||||
</el-table-column>
|
<div class="card-tenant">{{ item.tenantName }}</div>
|
||||||
<el-table-column prop="deposit" label="押金" width="120">
|
<el-tag :type="item.subStatus === 'expired' ? 'danger' : item.subStatus === 'soon_expire' ? 'warning' : 'success'" size="small">
|
||||||
<template slot-scope="scope">¥{{ scope.row.deposit }}</template>
|
{{ subStatusLabels[item.subStatus] }}
|
||||||
</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] }}
|
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</div>
|
||||||
</el-table-column>
|
<div class="card-dates">
|
||||||
<el-table-column label="操作" width="80">
|
<span class="date-range">{{ item.startDate }} → {{ item.endDate }}</span>
|
||||||
<template slot-scope="scope">
|
</div>
|
||||||
<el-button type="text" size="small" @click="goToDetail(scope.row.roomId)">查看</el-button>
|
<div class="card-amounts">
|
||||||
</template>
|
<span class="amount-item">¥{{ item.rent }}/月</span>
|
||||||
</el-table-column>
|
<span class="amount-divider"></span>
|
||||||
</el-table>
|
<span class="amount-item">押金 ¥{{ item.deposit }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
<el-empty v-if="selectedDayItems.length === 0" description="暂无数据"></el-empty>
|
||||||
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -101,7 +100,8 @@ export default {
|
||||||
apartments: [],
|
apartments: [],
|
||||||
filterApartmentId: null,
|
filterApartmentId: null,
|
||||||
filterTenantName: '',
|
filterTenantName: '',
|
||||||
filterSubStatus: ''
|
filterSubStatus: '',
|
||||||
|
_afterLoadCallback: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -122,6 +122,7 @@ export default {
|
||||||
tenantName: item.tenantName,
|
tenantName: item.tenantName,
|
||||||
roomNumber: item.roomNumber,
|
roomNumber: item.roomNumber,
|
||||||
apartmentName: item.apartmentName,
|
apartmentName: item.apartmentName,
|
||||||
|
startDate: item.startDate,
|
||||||
endDate: item.endDate,
|
endDate: item.endDate,
|
||||||
status: item.status,
|
status: item.status,
|
||||||
subStatus,
|
subStatus,
|
||||||
|
|
@ -156,12 +157,20 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadApartments()
|
this.loadApartments()
|
||||||
this.loadData()
|
this.loadData()
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this._setupTodayListener()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this._cleanupTodayListener()
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentDate(newVal, oldVal) {
|
currentDate(newVal, oldVal) {
|
||||||
if (!oldVal || newVal.getMonth() !== oldVal.getMonth() || newVal.getFullYear() !== oldVal.getFullYear()) {
|
if (!oldVal || newVal.getMonth() !== oldVal.getMonth() || newVal.getFullYear() !== oldVal.getFullYear()) {
|
||||||
|
if (!this._afterLoadCallback) {
|
||||||
this.selectedDate = null
|
this.selectedDate = null
|
||||||
this.dayDialogVisible = false
|
this.dayDialogVisible = false
|
||||||
|
}
|
||||||
this.loadData()
|
this.loadData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -190,6 +199,10 @@ export default {
|
||||||
this.rentals = []
|
this.rentals = []
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
if (this._afterLoadCallback) {
|
||||||
|
this._afterLoadCallback()
|
||||||
|
this._afterLoadCallback = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleFilterChange() {
|
handleFilterChange() {
|
||||||
|
|
@ -216,6 +229,29 @@ export default {
|
||||||
},
|
},
|
||||||
goToDetail(roomId) {
|
goToDetail(roomId) {
|
||||||
this.$router.push(`/rental/detail/${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 {
|
::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 {
|
::v-deep .el-calendar-table td.is-today .calendar-cell .cell-day {
|
||||||
|
|
@ -397,6 +433,7 @@ export default {
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep .el-calendar-table td.is-selected {
|
::v-deep .el-calendar-table td.is-selected {
|
||||||
|
|
@ -458,7 +495,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-item {
|
.cell-item {
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
@ -570,4 +607,121 @@ export default {
|
||||||
::v-deep .el-dialog__body {
|
::v-deep .el-dialog__body {
|
||||||
padding: 20px 24px;
|
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>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue