pet/pages/profile/family.vue

909 lines
19 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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="family-container page-container-with-bg">
<!-- 家庭概览卡片 -->
<view class="family-overview-card">
<view class="overview-header">
<view class="family-info">
<text class="family-name">{{ familyData.name || '我的家庭' }}</text>
<text class="family-desc">{{ familyData.members.length }}位成员 · 共同照顾{{ petCount }}只宠物</text>
</view>
<view class="family-avatar">
<text class="avatar-icon">👨👩👧👦</text>
</view>
</view>
<view class="overview-stats">
<view class="stat-item">
<view class="stat-number">{{ familyData.members.length }}</view>
<view class="stat-label">家庭成员</view>
</view>
<view class="stat-item">
<view class="stat-number">{{ sharedPets }}</view>
<view class="stat-label">共享宠物</view>
</view>
<view class="stat-item">
<view class="stat-number">{{ todayRecords }}</view>
<view class="stat-label">今日记录</view>
</view>
</view>
</view>
<!-- 家庭成员卡片 -->
<view class="members-card">
<view class="card-header">
<text class="card-title">家庭成员</text>
<view class="header-action" @click="inviteMember">
<text class="action-icon"></text>
<text class="action-text">邀请</text>
</view>
</view>
<view class="members-list">
<view
class="member-item"
v-for="member in familyData.members"
:key="member.id"
@click="viewMemberDetail(member)"
>
<view class="member-avatar">
<u-avatar
:src="member.avatarUrl || ''"
:text="member.nickName ? member.nickName.charAt(0) : '👤'"
size="80"
shape="circle"
bg-color="linear-gradient(135deg, #FF8A80, #FFB6C1)"
color="white"
font-size="32"
></u-avatar>
<view class="member-role-badge" :class="member.role">
<text class="role-text">{{ getRoleText(member.role) }}</text>
</view>
</view>
<view class="member-info">
<view class="member-name">{{ member.nickName }}</view>
<view class="member-status">
<view class="status-dot" :class="{ online: member.isOnline }"></view>
<text class="status-text">{{ member.isOnline ? '在线' : '离线' }}</text>
</view>
<view class="member-stats">
<text class="stats-text">本月记录 {{ member.monthlyRecords || 0 }} 条</text>
</view>
</view>
<view class="member-action">
<text class="action-arrow">→</text>
</view>
</view>
</view>
</view>
<!-- 权限设置卡片 -->
<view class="permissions-card">
<view class="card-header">
<text class="card-title">权限设置</text>
</view>
<view class="permissions-list">
<view class="permission-item">
<view class="permission-info">
<text class="permission-title">宠物管理权限</text>
<text class="permission-desc">允许成员添加、编辑宠物信息</text>
</view>
<view class="permission-switch">
<u-switch
v-model="permissions.petManagement"
@change="onPermissionChange('petManagement', $event)"
active-color="#FF8A80"
></u-switch>
</view>
</view>
<view class="permission-item">
<view class="permission-info">
<text class="permission-title">记录管理权限</text>
<text class="permission-desc">允许成员添加、编辑宠物记录</text>
</view>
<view class="permission-switch">
<u-switch
v-model="permissions.recordManagement"
@change="onPermissionChange('recordManagement', $event)"
active-color="#FF8A80"
></u-switch>
</view>
</view>
<view class="permission-item">
<view class="permission-info">
<text class="permission-title">成员管理权限</text>
<text class="permission-desc">允许成员邀请和管理其他成员</text>
</view>
<view class="permission-switch">
<u-switch
v-model="permissions.memberManagement"
@change="onPermissionChange('memberManagement', $event)"
active-color="#FF8A80"
></u-switch>
</view>
</view>
</view>
</view>
<!-- 家庭设置卡片 -->
<view class="settings-card">
<view class="card-header">
<text class="card-title">家庭设置</text>
</view>
<view class="settings-list">
<view class="setting-item" @click="editFamilyName">
<view class="setting-left">
<text class="setting-icon">✏️</text>
<text class="setting-text">修改家庭名称</text>
</view>
<view class="setting-right">
<text class="setting-value">{{ familyData.name || '我的家庭' }}</text>
<text class="setting-arrow">→</text>
</view>
</view>
<view class="setting-item" @click="viewInviteCode">
<view class="setting-left">
<text class="setting-icon">🔗</text>
<text class="setting-text">邀请码管理</text>
</view>
<view class="setting-right">
<text class="setting-arrow">→</text>
</view>
</view>
<view class="setting-item" @click="viewFamilyHistory">
<view class="setting-left">
<text class="setting-icon">📊</text>
<text class="setting-text">家庭统计</text>
</view>
<view class="setting-right">
<text class="setting-arrow">→</text>
</view>
</view>
<view class="setting-item danger" @click="leaveFamilyConfirm">
<view class="setting-left">
<text class="setting-icon">🚪</text>
<text class="setting-text">退出家庭</text>
</view>
<view class="setting-right">
<text class="setting-arrow">→</text>
</view>
</view>
</view>
</view>
<!-- 邀请成员弹窗 -->
<u-modal
:show="showInviteModal"
title="邀请家庭成员"
@confirm="confirmInvite"
@cancel="showInviteModal = false"
>
<view class="invite-content">
<view class="invite-method">
<text class="method-title">邀请方式</text>
<view class="method-options">
<view class="method-option" @click="shareInviteCode">
<text class="option-icon">📱</text>
<text class="option-text">分享邀请码</text>
</view>
<view class="method-option" @click="shareQRCode">
<text class="option-icon">📷</text>
<text class="option-text">分享二维码</text>
</view>
</view>
</view>
<view class="invite-code">
<text class="code-title">邀请码</text>
<view class="code-display">
<text class="code-text">{{ inviteCode }}</text>
<view class="code-copy" @click="copyInviteCode">
<text class="copy-text">复制</text>
</view>
</view>
</view>
</view>
</u-modal>
</view>
</template>
<script>
import { reactive, ref, onMounted, computed } from 'vue'
export default {
name: 'FamilyPage',
setup() {
// 响应式数据
const familyData = reactive({
id: '',
name: '我的家庭',
members: []
})
const permissions = reactive({
petManagement: true,
recordManagement: true,
memberManagement: false
})
const showInviteModal = ref(false)
const inviteCode = ref('')
const petCount = ref(0)
const sharedPets = ref(0)
const todayRecords = ref(0)
// 生命周期
onMounted(() => {
loadFamilyData()
generateInviteCode()
})
// 方法定义
const loadFamilyData = () => {
try {
// 加载家庭数据
const savedFamilyData = uni.getStorageSync('familyData') || {
id: 'family_' + Date.now(),
name: '我的家庭',
members: []
}
// 如果没有成员,添加当前用户作为家庭主人
if (savedFamilyData.members.length === 0) {
const userInfo = uni.getStorageSync('userInfo') || {}
savedFamilyData.members.push({
id: 'user_' + Date.now(),
nickName: userInfo.nickName || '我',
avatarUrl: userInfo.avatarUrl || '',
role: 'owner',
isOnline: true,
joinTime: new Date().toISOString(),
monthlyRecords: 15
})
}
Object.assign(familyData, savedFamilyData)
// 加载权限设置
const savedPermissions = uni.getStorageSync('familyPermissions') || permissions
Object.assign(permissions, savedPermissions)
// 加载统计数据
loadFamilyStats()
} catch (error) {
console.error('加载家庭数据失败:', error)
}
}
const loadFamilyStats = () => {
// 加载宠物数量
const pets = uni.getStorageSync('pets') || []
petCount.value = pets.length
sharedPets.value = pets.filter(pet => pet.shared).length || pets.length
// 计算今日记录数
const today = new Date().toDateString()
let todayCount = 0
pets.forEach(pet => {
const records = uni.getStorageSync(`petRecords_${pet.id}`) || []
todayCount += records.filter(record =>
new Date(record.date).toDateString() === today
).length
})
todayRecords.value = todayCount
}
const generateInviteCode = () => {
// 生成6位邀请码
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
let code = ''
for (let i = 0; i < 6; i++) {
code += chars.charAt(Math.floor(Math.random() * chars.length))
}
inviteCode.value = code
}
const getRoleText = (role) => {
const roleMap = {
'owner': '主人',
'admin': '管理员',
'member': '成员',
'viewer': '观察者'
}
return roleMap[role] || '成员'
}
const inviteMember = () => {
showInviteModal.value = true
}
const confirmInvite = () => {
shareInviteCode()
showInviteModal.value = false
}
const shareInviteCode = () => {
const shareText = `邀请您加入我的宠物家庭!\n邀请码${inviteCode.value}\n快来一起记录我们的宠物生活吧~`
// 复制到剪贴板
uni.setClipboardData({
data: shareText,
success: () => {
uni.showToast({
title: '邀请信息已复制',
icon: 'success'
})
}
})
}
const shareQRCode = () => {
uni.showToast({
title: '二维码功能开发中',
icon: 'none'
})
}
const copyInviteCode = () => {
uni.setClipboardData({
data: inviteCode.value,
success: () => {
uni.showToast({
title: '邀请码已复制',
icon: 'success'
})
}
})
}
const viewMemberDetail = (member) => {
uni.showModal({
title: member.nickName,
content: `角色:${getRoleText(member.role)}\n加入时间${new Date(member.joinTime).toLocaleDateString()}\n本月记录${member.monthlyRecords || 0}`,
showCancel: false
})
}
const onPermissionChange = (key, value) => {
permissions[key] = value
uni.setStorageSync('familyPermissions', permissions)
uni.showToast({
title: '权限设置已更新',
icon: 'success'
})
}
const editFamilyName = () => {
uni.showModal({
title: '修改家庭名称',
editable: true,
placeholderText: '请输入家庭名称',
success: (res) => {
if (res.confirm && res.content.trim()) {
familyData.name = res.content.trim()
saveFamilyData()
uni.showToast({
title: '家庭名称已更新',
icon: 'success'
})
}
}
})
}
const viewInviteCode = () => {
showInviteModal.value = true
}
const viewFamilyHistory = () => {
uni.showToast({
title: '家庭统计功能开发中',
icon: 'none'
})
}
const leaveFamilyConfirm = () => {
uni.showModal({
title: '确认退出',
content: '退出后将无法查看家庭共享的宠物信息,确定要退出吗?',
success: (res) => {
if (res.confirm) {
leaveFamily()
}
}
})
}
const leaveFamily = () => {
// 清除家庭数据
uni.removeStorageSync('familyData')
uni.removeStorageSync('familyPermissions')
uni.showToast({
title: '已退出家庭',
icon: 'success'
})
// 返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
const saveFamilyData = () => {
uni.setStorageSync('familyData', familyData)
}
return {
familyData,
permissions,
showInviteModal,
inviteCode,
petCount,
sharedPets,
todayRecords,
getRoleText,
inviteMember,
confirmInvite,
shareInviteCode,
shareQRCode,
copyInviteCode,
viewMemberDetail,
onPermissionChange,
editFamilyName,
viewInviteCode,
viewFamilyHistory,
leaveFamilyConfirm
}
}
}
</script>
<style lang="scss" scoped>
.family-container {
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
min-height: 100vh;
padding-bottom: 40rpx;
}
/* 通用卡片样式 */
.family-overview-card,
.members-card,
.permissions-card,
.settings-card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
margin: 0 30rpx 24rpx 30rpx;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
border: 1rpx solid rgba(255, 255, 255, 0.3);
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333333;
}
.header-action {
display: flex;
align-items: center;
padding: 12rpx 20rpx;
background: rgba(255, 138, 128, 0.1);
border-radius: 20rpx;
.action-icon {
font-size: 24rpx;
margin-right: 8rpx;
}
.action-text {
font-size: 24rpx;
color: #FF8A80;
}
}
}
}
/* 家庭概览卡片 */
.family-overview-card {
.overview-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 32rpx;
.family-info {
flex: 1;
.family-name {
display: block;
font-size: 36rpx;
font-weight: 700;
color: #333333;
margin-bottom: 8rpx;
}
.family-desc {
font-size: 24rpx;
color: #666666;
}
}
.family-avatar {
.avatar-icon {
font-size: 60rpx;
}
}
}
.overview-stats {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 24rpx;
.stat-item {
text-align: center;
padding: 24rpx 16rpx;
border-radius: 16rpx;
background: rgba(255, 138, 128, 0.05);
.stat-number {
font-size: 40rpx;
font-weight: 700;
color: #FF8A80;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 22rpx;
color: #666666;
}
}
}
}
/* 家庭成员卡片 */
.members-list {
.member-item {
display: flex;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
&:last-child {
border-bottom: none;
}
&:active {
background: rgba(255, 138, 128, 0.05);
border-radius: 16rpx;
}
.member-avatar {
position: relative;
margin-right: 24rpx;
.member-role-badge {
position: absolute;
bottom: -4rpx;
right: -4rpx;
padding: 4rpx 8rpx;
border-radius: 12rpx;
font-size: 18rpx;
&.owner {
background: #FFD700;
color: #333333;
}
&.admin {
background: #FF8A80;
color: white;
}
&.member {
background: #81C784;
color: white;
}
&.viewer {
background: #CCCCCC;
color: #666666;
}
.role-text {
font-size: 18rpx;
}
}
}
.member-info {
flex: 1;
.member-name {
font-size: 30rpx;
font-weight: 600;
color: #333333;
margin-bottom: 8rpx;
}
.member-status {
display: flex;
align-items: center;
margin-bottom: 4rpx;
.status-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #CCCCCC;
margin-right: 8rpx;
&.online {
background: #4CAF50;
}
}
.status-text {
font-size: 22rpx;
color: #666666;
}
}
.member-stats {
.stats-text {
font-size: 20rpx;
color: #999999;
}
}
}
.member-action {
.action-arrow {
font-size: 24rpx;
color: #FF8A80;
}
}
}
}
/* 权限设置卡片 */
.permissions-list {
.permission-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
&:last-child {
border-bottom: none;
}
.permission-info {
flex: 1;
.permission-title {
display: block;
font-size: 30rpx;
font-weight: 500;
color: #333333;
margin-bottom: 8rpx;
}
.permission-desc {
font-size: 24rpx;
color: #666666;
line-height: 1.4;
}
}
.permission-switch {
margin-left: 24rpx;
}
}
}
/* 家庭设置卡片 */
.settings-list {
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx 0;
border-bottom: 1rpx solid rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
&:last-child {
border-bottom: none;
}
&:active {
background: rgba(255, 138, 128, 0.05);
border-radius: 16rpx;
}
&.danger {
.setting-text {
color: #FF5722;
}
.setting-arrow {
color: #FF5722;
}
}
.setting-left {
display: flex;
align-items: center;
.setting-icon {
font-size: 32rpx;
margin-right: 16rpx;
}
.setting-text {
font-size: 30rpx;
color: #333333;
font-weight: 500;
}
}
.setting-right {
display: flex;
align-items: center;
.setting-value {
font-size: 24rpx;
color: #666666;
margin-right: 12rpx;
}
.setting-arrow {
font-size: 24rpx;
color: #FF8A80;
}
}
}
}
/* 邀请弹窗样式 */
.invite-content {
padding: 20rpx 0;
.invite-method {
margin-bottom: 32rpx;
.method-title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333333;
margin-bottom: 16rpx;
}
.method-options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
.method-option {
display: flex;
flex-direction: column;
align-items: center;
padding: 24rpx 16rpx;
border-radius: 16rpx;
background: rgba(255, 138, 128, 0.05);
transition: all 0.3s ease;
&:active {
background: rgba(255, 138, 128, 0.1);
transform: scale(0.98);
}
.option-icon {
font-size: 32rpx;
margin-bottom: 8rpx;
}
.option-text {
font-size: 24rpx;
color: #333333;
}
}
}
}
.invite-code {
.code-title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333333;
margin-bottom: 16rpx;
}
.code-display {
display: flex;
align-items: center;
padding: 20rpx;
background: rgba(255, 138, 128, 0.05);
border-radius: 16rpx;
border: 2rpx dashed #FF8A80;
.code-text {
flex: 1;
font-size: 32rpx;
font-weight: 700;
color: #FF8A80;
text-align: center;
letter-spacing: 4rpx;
}
.code-copy {
padding: 8rpx 16rpx;
background: #FF8A80;
border-radius: 12rpx;
.copy-text {
font-size: 22rpx;
color: white;
}
}
}
}
}
/* 响应式设计 */
@media (max-width: 375px) {
.family-container {
.family-overview-card,
.members-card,
.permissions-card,
.settings-card {
margin: 0 20rpx 20rpx 20rpx;
padding: 24rpx;
}
.overview-stats {
gap: 16rpx;
.stat-item {
padding: 20rpx 12rpx;
.stat-number {
font-size: 36rpx;
}
}
}
}
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.family-container > view {
animation: fadeIn 0.5s ease-out;
}
.family-container > view:nth-child(1) { animation-delay: 0.1s; }
.family-container > view:nth-child(2) { animation-delay: 0.2s; }
.family-container > view:nth-child(3) { animation-delay: 0.3s; }
.family-container > view:nth-child(4) { animation-delay: 0.4s; }
</style>