rentease-app/pages/profile/profile.vue

872 lines
24 KiB
Vue
Raw Normal View History

2026-04-20 06:23:11 +00:00
<template>
<view class="profile-page">
<!-- 用户信息卡片 -->
<view class="user-card">
<view class="user-bg"></view>
<view class="user-content">
<view class="user-avatar" @click="editAvatar">
<image v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill"></image>
<view v-else class="avatar-placeholder">
<text class="avatar-text">{{getAvatarText(userInfo.nickname)}}</text>
</view>
<view class="edit-badge">
<uni-icons type="camera-filled" size="16" color="#FFFFFF"></uni-icons>
</view>
</view>
<view class="user-info" @click="editProfile">
<text class="user-name">{{userInfo.nickname || '未设置昵称'}}</text>
<text class="user-phone">{{maskPhone(userInfo.phone)}}</text>
<view class="edit-hint">
<uni-icons type="compose" size="12" color="rgba(255,255,255,0.6)"></uni-icons>
<text>点击编辑资料</text>
</view>
</view>
<view class="user-level">
<view class="level-badge">
<uni-icons type="vip-filled" size="16" color="#F59E0B"></uni-icons>
<text>房东版</text>
</view>
</view>
</view>
</view>
<scroll-view scroll-y class="page-content" @scrolltolower="loadMoreStats">
<!-- 数据概览 -->
<view class="stats-section">
<view class="stats-card">
<view class="stat-item" @click="navigateTo('/pages/apartments/apartments')">
<text class="stat-num">{{userStats.apartments}}</text>
<text class="stat-label">公寓</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item" @click="navigateTo('/pages/rooms/rooms')">
<text class="stat-num">{{userStats.rooms}}</text>
<text class="stat-label">房源</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item" @click="navigateTo('/pages/renters/renters')">
<text class="stat-num">{{userStats.renters}}</text>
<text class="stat-label">租客</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item" @click="navigateTo('/pages/rentals/rentals')">
<text class="stat-num">{{userStats.rentals}}</text>
<text class="stat-label">租约</text>
</view>
</view>
</view>
<!-- 快捷操作 -->
<view class="quick-actions">
<view class="action-grid">
<view class="action-item" @click="navigateTo('/pages/add-record/add-record')">
<view class="action-icon add">
<uni-icons type="plus-filled" size="28" color="#FFFFFF"></uni-icons>
</view>
<text class="action-text">新增租约</text>
</view>
<view class="action-item" @click="navigateTo('/pages/room-add/room-add')">
<view class="action-icon room">
<uni-icons type="home-filled" size="28" color="#FFFFFF"></uni-icons>
</view>
<text class="action-text">添加房源</text>
</view>
<view class="action-item" @click="navigateTo('/pages/bills/bills')">
<view class="action-icon bill">
<uni-icons type="wallet-filled" size="28" color="#FFFFFF"></uni-icons>
</view>
<text class="action-text">记一笔</text>
</view>
<view class="action-item" @click="navigateTo('/pages/stats/stats')">
<view class="action-icon stats">
<uni-icons type="chart-filled" size="28" color="#FFFFFF"></uni-icons>
</view>
<text class="action-text">收支统计</text>
</view>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-section">
<view class="menu-group">
<view class="menu-title">财务管理</view>
<view class="menu-list">
<view class="menu-item" @click="navigateTo('/pages/bills/bills')">
<view class="menu-icon bill">
<uni-icons type="list" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">账单管理</text>
<view class="menu-extra" v-if="userStats.unpaidBills > 0">
<text class="badge">{{userStats.unpaidBills}}</text>
</view>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
<view class="menu-item" @click="navigateTo('/pages/rentals/rentals')">
<view class="menu-icon contract">
<uni-icons type="compose" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">租约管理</text>
<view class="menu-extra" v-if="userStats.expiringRentals > 0">
<text class="badge orange">{{userStats.expiringRentals}}</text>
</view>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
<view class="menu-item" @click="navigateTo('/pages/meter-readings/meter-readings')">
<view class="menu-icon meter">
<uni-icons type="settings-filled" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">抄表记录</text>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
</view>
</view>
<view class="menu-group">
<view class="menu-title">房源管理</view>
<view class="menu-list">
<view class="menu-item" @click="navigateTo('/pages/apartments/apartments')">
<view class="menu-icon property">
<uni-icons type="home-filled" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">公寓管理</text>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
<view class="menu-item" @click="navigateTo('/pages/rooms/rooms')">
<view class="menu-icon room">
<uni-icons type="shop-filled" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">房源管理</text>
<view class="menu-extra">
<text class="extra-text">{{userStats.emptyRooms}}间空房</text>
</view>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
<view class="menu-item" @click="navigateTo('/pages/renters/renters')">
<view class="menu-icon tenant">
<uni-icons type="personadd-filled" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">租客管理</text>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
</view>
</view>
<view class="menu-group">
<view class="menu-title">计费中心</view>
<view class="menu-list">
<view class="menu-item" @click="navigateTo('/pages/billing/billing-center')">
<view class="menu-icon billing">
<uni-icons type="vip-filled" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">我的套餐</text>
<view class="menu-extra">
<text class="billing-status" :class="billingStatusClass">{{billingStatusText}}</text>
</view>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
<view class="menu-item" @click="navigateTo('/pages/billing/order-list')">
<view class="menu-icon order">
<uni-icons type="list-filled" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">我的订单</text>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
<view class="menu-item" @click="navigateTo('/pages/billing/payment-record')">
<view class="menu-icon payment">
<uni-icons type="wallet-filled" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">支付记录</text>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
</view>
</view>
<view class="menu-group">
<view class="menu-title">系统设置</view>
<view class="menu-list">
<view class="menu-item" @click="navigateTo('/pages/settings/settings')">
<view class="menu-icon setting">
<uni-icons type="gear-filled" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">账号设置</text>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
<view class="menu-item" @click="navigateTo('/pages/settings/categories')">
<view class="menu-icon category">
<uni-icons type="folder-add-filled" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">收支类目</text>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
<view class="menu-item" @click="showAbout">
<view class="menu-icon about">
<uni-icons type="info-filled" size="24" color="#FFFFFF"></uni-icons>
</view>
<text class="menu-text">关于我们</text>
<view class="menu-extra">
<text class="version">v{{version}}</text>
<uni-icons type="right" size="16" color="#94A3B8"></uni-icons>
</view>
</view>
</view>
</view>
</view>
<!-- 退出登录 -->
<view class="logout-section">
<button class="logout-btn" @click="logout">退出登录</button>
</view>
<view class="safe-area-bottom" style="height: 40rpx;"></view>
</scroll-view>
</view>
</template>
<script>
import { apartmentApi, roomApi, renterApi, rentalApi, billApi, billingApi } from '../../api/index.js'
export default {
data() {
return {
userInfo: {},
userStats: {
apartments: 0,
rooms: 0,
renters: 0,
rentals: 0,
emptyRooms: 0,
unpaidBills: 0,
expiringRentals: 0
},
billingInfo: null,
version: '1.0.0',
isLoading: false
}
},
onLoad() {
this.loadUserInfo()
this.loadUserStats()
this.loadBillingInfo()
},
onShow() {
this.loadUserInfo()
this.loadUserStats()
this.loadBillingInfo()
},
onPullDownRefresh() {
this.loadUserStats().finally(() => {
uni.stopPullDownRefresh()
})
},
computed: {
billingStatusText() {
if (!this.billingInfo) return '加载中...'
const status = this.billingInfo.billingStatus
if (status === 'trial_active') return '试用期'
if (status === 'paid_active') return '付费期'
if (status === 'trial_expired' || status === 'paid_expired') return '已过期'
return '未知'
},
billingStatusClass() {
if (!this.billingInfo) return ''
const status = this.billingInfo.billingStatus
if (status === 'trial_active') return 'trial'
if (status === 'paid_active') return 'active'
if (status === 'trial_expired' || status === 'paid_expired') return 'expired'
return ''
}
},
methods: {
// 加载用户信息
loadUserInfo() {
const userInfo = uni.getStorageSync('userInfo') || {}
this.userInfo = userInfo
},
// 加载用户统计数据
async loadUserStats() {
if (this.isLoading) return
this.isLoading = true
try {
// 并行加载所有统计数据
const [apartmentRes, roomRes, renterRes, rentalRes, billRes] = await Promise.all([
apartmentApi.list(),
roomApi.list(),
renterApi.list(),
rentalApi.list(),
billApi.list({ status: 'unpaid' })
])
const apartments = apartmentRes.data || []
const rooms = roomRes.data || []
const renters = renterRes.data || []
const rentals = rentalRes.data || []
const unpaidBills = billRes.data || []
// 计算空房数量
const emptyRooms = rooms.filter(r => r.status === 'empty').length
// 计算即将到期的租约30天内
const now = new Date()
const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000)
const expiringRentals = rentals.filter(r => {
if (r.status !== 'active') return false
const endDate = new Date(r.endDate)
return endDate <= thirtyDaysLater && endDate >= now
}).length
this.userStats = {
apartments: apartments.length,
rooms: rooms.length,
renters: renters.length,
rentals: rentals.length,
emptyRooms,
unpaidBills: unpaidBills.length,
expiringRentals
}
} catch (error) {
console.error('加载统计数据失败:', error)
} finally {
this.isLoading = false
}
},
// 获取头像文字
getAvatarText(nickname) {
if (!nickname) return '用'
return nickname.charAt(0).toUpperCase()
},
// 掩码手机号
maskPhone(phone) {
if (!phone) return '未绑定手机号'
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
},
// 编辑头像
editAvatar() {
uni.showActionSheet({
itemList: ['拍照', '从相册选择'],
success: (res) => {
const sourceType = res.tapIndex === 0 ? ['camera'] : ['album']
uni.chooseImage({
count: 1,
sourceType,
success: (res) => {
this.uploadAvatar(res.tempFilePaths[0])
}
})
}
})
},
// 上传头像
async uploadAvatar(filePath) {
uni.showLoading({ title: '上传中...' })
try {
// 这里应该调用实际上传接口
// const uploadRes = await uploadFile(filePath)
// this.userInfo.avatar = uploadRes.url
// 模拟上传成功
this.userInfo.avatar = filePath
uni.setStorageSync('userInfo', this.userInfo)
uni.showToast({ title: '头像更新成功', icon: 'success' })
} catch (error) {
uni.showToast({ title: '上传失败', icon: 'none' })
} finally {
uni.hideLoading()
}
},
// 编辑个人资料
editProfile() {
uni.navigateTo({
url: '/pages/profile-edit/profile-edit'
})
},
// 页面导航
navigateTo(url) {
uni.navigateTo({ url })
},
// 显示关于
showAbout() {
uni.showModal({
title: '关于 RentEase',
content: `RentEase 智能租赁管理平台\n版本: v${this.version}\n\n让租赁管理更简单、更高效`,
showCancel: false
})
},
// 退出登录
logout() {
uni.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
uni.reLaunch({ url: '/pages/login/login' })
}
}
})
},
// 加载更多统计
loadMoreStats() {
// 可以在这里加载更多历史统计数据
},
// 加载计费信息
async loadBillingInfo() {
try {
const res = await billingApi.getInfo()
if (res.code === 200) {
this.billingInfo = res.data.tenant
}
} catch (error) {
console.error('加载计费信息失败:', error)
}
}
}
}
</script>
<style scoped>
.profile-page {
min-height: 100vh;
background: #F8FAFC;
display: flex;
flex-direction: column;
width: 100%;
max-width: 100vw;
overflow-x: hidden;
}
/* 用户卡片 */
.user-card {
position: relative;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40rpx 32rpx 80rpx;
border-radius: 0 0 40rpx 40rpx;
width: 100%;
box-sizing: border-box;
}
.user-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
border-radius: 0 0 40rpx 40rpx;
}
.user-bg::before {
content: '';
position: absolute;
width: 400rpx;
height: 400rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
top: -100rpx;
right: -100rpx;
}
.user-bg::after {
content: '';
position: absolute;
width: 300rpx;
height: 300rpx;
background: rgba(255, 255, 255, 0.05);
border-radius: 50%;
bottom: -50rpx;
left: -50rpx;
}
.user-content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.user-avatar {
position: relative;
width: 160rpx;
height: 160rpx;
border-radius: 50%;
overflow: hidden;
border: 6rpx solid rgba(255, 255, 255, 0.3);
margin-bottom: 24rpx;
}
.user-avatar image {
width: 100%;
height: 100%;
}
.avatar-placeholder {
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
.avatar-text {
font-size: 64rpx;
font-weight: 700;
color: #FFFFFF;
}
.edit-badge {
position: absolute;
bottom: 0;
right: 0;
width: 48rpx;
height: 48rpx;
background: #667eea;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 4rpx solid #FFFFFF;
}
.user-info {
text-align: center;
margin-bottom: 16rpx;
}
.user-name {
display: block;
font-size: 40rpx;
font-weight: 700;
color: #FFFFFF;
margin-bottom: 8rpx;
}
.user-phone {
display: block;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 8rpx;
}
.edit-hint {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
font-size: 22rpx;
color: rgba(255, 255, 255, 0.6);
}
.level-badge {
display: flex;
align-items: center;
gap: 8rpx;
background: rgba(255, 255, 255, 0.2);
padding: 12rpx 24rpx;
border-radius: 30rpx;
font-size: 24rpx;
color: #FFFFFF;
}
/* 页面内容 */
.page-content {
flex: 1;
padding: 0 32rpx;
margin-top: -50rpx;
width: 100%;
box-sizing: border-box;
}
/* 统计区域 */
.stats-section {
margin-bottom: 32rpx;
}
.stats-card {
background: #FFFFFF;
border-radius: 24rpx;
padding: 32rpx 24rpx;
display: flex;
align-items: center;
justify-content: space-around;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 16rpx;
}
.stat-num {
font-size: 40rpx;
font-weight: 700;
color: #667eea;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #64748B;
}
.stat-divider {
width: 2rpx;
height: 60rpx;
background: #E2E8F0;
}
/* 快捷操作 */
.quick-actions {
margin-bottom: 32rpx;
}
.action-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24rpx;
background: #FFFFFF;
border-radius: 24rpx;
padding: 32rpx 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
}
.action-icon {
width: 96rpx;
height: 96rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
}
.action-icon.add {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.action-icon.room {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.action-icon.bill {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.action-icon.stats {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
.action-text {
font-size: 24rpx;
color: #1E293B;
font-weight: 500;
}
/* 菜单区域 */
.menu-section {
margin-bottom: 32rpx;
}
.menu-group {
margin-bottom: 24rpx;
}
.menu-title {
font-size: 28rpx;
font-weight: 600;
color: #94A3B8;
margin-bottom: 16rpx;
padding-left: 16rpx;
}
.menu-list {
background: #FFFFFF;
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
}
.menu-item {
display: flex;
align-items: center;
padding: 28rpx 32rpx;
border-bottom: 2rpx solid #F8FAFC;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-item:active {
background: #F8FAFC;
}
.menu-icon {
width: 56rpx;
height: 56rpx;
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
}
.menu-icon.bill {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.menu-icon.contract {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.menu-icon.meter {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.menu-icon.property {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.menu-icon.room {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
.menu-icon.tenant {
background: linear-gradient(135deg, #30cfd0 0%, #330867 100%);
}
.menu-icon.setting {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
}
.menu-icon.category {
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
}
.menu-icon.about {
background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%);
}
.menu-icon.billing {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.menu-icon.order {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
.menu-icon.payment {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.billing-status {
font-size: 24rpx;
padding: 6rpx 16rpx;
border-radius: 8rpx;
font-weight: 500;
}
.billing-status.trial {
background: #FEF3C7;
color: #D97706;
}
.billing-status.active {
background: #D1FAE5;
color: #059669;
}
.billing-status.expired {
background: #FEE2E2;
color: #DC2626;
}
.menu-text {
flex: 1;
font-size: 30rpx;
color: #1E293B;
font-weight: 500;
}
.menu-extra {
display: flex;
align-items: center;
gap: 16rpx;
margin-right: 16rpx;
}
.extra-text {
font-size: 26rpx;
color: #667eea;
font-weight: 500;
}
.badge {
min-width: 36rpx;
height: 36rpx;
background: #EF4444;
color: #FFFFFF;
font-size: 20rpx;
font-weight: 600;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 10rpx;
}
.badge.orange {
background: #F59E0B;
}
.version {
font-size: 26rpx;
color: #94A3B8;
}
/* 退出登录 */
.logout-section {
margin-bottom: 32rpx;
}
.logout-btn {
width: 100%;
height: 96rpx;
background: #FEE2E2;
color: #EF4444;
font-size: 32rpx;
font-weight: 600;
border-radius: 16rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.logout-btn:active {
background: #FECACA;
}
</style>