rentease-app/pages/profile/profile.vue

872 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>