|
|
@@ -1,226 +1,226 @@
|
|
|
<template>
|
|
|
- <view class="member-strip">
|
|
|
- <!-- 浮标:家庭图标(默认态)→ 当前成员头像(展开态) -->
|
|
|
- <view class="fab-wrap">
|
|
|
- <view class="fab-avatar" :class="{ active: fanOpen }" @tap="toggleFan">
|
|
|
- <!-- 默认态:家庭图标 -->
|
|
|
- <view class="fab-icon-default" v-if="!fanOpen">
|
|
|
- <text class="fab-icon-emoji">👨👩👧👦</text>
|
|
|
- </view>
|
|
|
- <!-- 展开态:当前成员头像 -->
|
|
|
- <image class="fab-img" :class="{ 'fab-img-show': fanOpen }" :src="currentMember.avatar || defaultAvatar" mode="aspectFill" />
|
|
|
- <view class="fab-ring" :class="{ 'ring-active': fanOpen }"></view>
|
|
|
- <view class="fab-badge" v-if="pendingCount > 0 && isAdmin" @tap.stop="openRequests">
|
|
|
- <text class="badge-count">{{ pendingCount }}</text>
|
|
|
+ <view class="family-member-strip">
|
|
|
+ <!-- 有成员:滚动条 / 居中模式 + 长按菜单 -->
|
|
|
+ <view v-if="renderedMembers && renderedMembers.length > 0">
|
|
|
+ <scroll-view
|
|
|
+ class="strip-scroll"
|
|
|
+ scroll-x
|
|
|
+ :scroll-with-animation="true"
|
|
|
+ :enhanced="true"
|
|
|
+ show-scrollbar="false"
|
|
|
+ v-if="renderedMembers.length >= 4"
|
|
|
+ >
|
|
|
+ <view class="strip-inner scroll-mode">
|
|
|
+ <FamilyMemberCard
|
|
|
+ v-for="member in renderedMembers"
|
|
|
+ :key="member.id"
|
|
|
+ :member="member"
|
|
|
+ :isSelected="member.id === selectedMemberId"
|
|
|
+ :energyScore="getMemberEnergy(member)"
|
|
|
+ @select="onSelect"
|
|
|
+ @longpress="onLongPress"
|
|
|
+ />
|
|
|
</view>
|
|
|
+ </scroll-view>
|
|
|
+ <view class="strip-inner center-mode" v-else>
|
|
|
+ <FamilyMemberCard
|
|
|
+ v-for="member in renderedMembers"
|
|
|
+ :key="member.id"
|
|
|
+ :member="member"
|
|
|
+ :isSelected="member.id === selectedMemberId"
|
|
|
+ :energyScore="getMemberEnergy(member)"
|
|
|
+ @select="onSelect"
|
|
|
+ @longpress="onLongPress"
|
|
|
+ />
|
|
|
</view>
|
|
|
- <view class="fab-pulse" v-if="!fanOpen"></view>
|
|
|
- </view>
|
|
|
|
|
|
- <!-- 扇形展开成员 -->
|
|
|
- <view v-if="fanOpen" class="fan-wrap show">
|
|
|
- <view class="fan-overlay" @tap="closeFan"></view>
|
|
|
- <view class="fan-container">
|
|
|
- <view
|
|
|
- class="fan-card"
|
|
|
- v-for="(member, index) in members"
|
|
|
- :key="member.id"
|
|
|
- :style="getFanPosition(member, index)"
|
|
|
- :class="{ selected: member.id === selectedMemberId }"
|
|
|
- @tap="selectMember(member)"
|
|
|
- @longpress="onFanLongPress(member)"
|
|
|
- >
|
|
|
- <view class="fan-avatar-wrap">
|
|
|
- <image class="fan-avatar" :src="member.avatar || defaultAvatar" mode="aspectFill" />
|
|
|
- <view class="fan-relation-badge" :style="{ background: getRelationColor(member) }">
|
|
|
- <text class="fan-relation-text">{{ member.relationship || member.relativeLabel || member.roleLabel || '成员' }}</text>
|
|
|
- </view>
|
|
|
- <view class="fan-spouse-mark" v-if="member.isSpouse">
|
|
|
- <text class="spouse-icon">💍</text>
|
|
|
+ <!-- 长按操作菜单 -->
|
|
|
+ <view class="action-overlay" v-if="showActionMenu" @click="closeMenu"></view>
|
|
|
+ <view class="action-sheet" v-if="showActionMenu">
|
|
|
+ <view class="action-header">
|
|
|
+ <image class="action-avatar" :src="actionMember ? (actionMember.avatar || '/static/default-avatar.png') : ''" mode="aspectFill" />
|
|
|
+ <view class="action-header-info">
|
|
|
+ <text class="action-title">{{ actionMember ? actionMember.nickname : '' }}</text>
|
|
|
+ <text class="action-relation">{{ actionMember ? (actionMember.relationship || actionMember.relativeLabel || actionMember.roleLabel || '') : '' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 关系现状区块 -->
|
|
|
+ <view class="action-scores" v-if="actionMember">
|
|
|
+ <view class="score-row">
|
|
|
+ <text class="score-label">信任度</text>
|
|
|
+ <view class="score-bar-track">
|
|
|
+ <view class="score-bar-fill trust" :style="{ width: scoreWidths[0] }"></view>
|
|
|
</view>
|
|
|
+ <text class="score-value">{{ scoreValues[0] }}</text>
|
|
|
</view>
|
|
|
- <text class="fan-nickname">{{ member.nickname }}</text>
|
|
|
- <view class="fan-score" v-if="getMemberEnergy(member) !== null">
|
|
|
- <text class="score-value">{{ getMemberEnergy(member) }}</text>
|
|
|
- <text class="score-unit">分</text>
|
|
|
+ <view class="score-row">
|
|
|
+ <text class="score-label">亲密度</text>
|
|
|
+ <view class="score-bar-track">
|
|
|
+ <view class="score-bar-fill intimacy" :style="{ width: scoreWidths[1] }"></view>
|
|
|
+ </view>
|
|
|
+ <text class="score-value">{{ scoreValues[1] }}</text>
|
|
|
</view>
|
|
|
- <view class="fan-selected-mark" v-if="member.id === selectedMemberId">
|
|
|
- <text class="mark-icon">✓</text>
|
|
|
+ <view class="score-row">
|
|
|
+ <text class="score-label">沟通质量</text>
|
|
|
+ <view class="score-bar-track">
|
|
|
+ <view class="score-bar-fill communication" :style="{ width: scoreWidths[2] }"></view>
|
|
|
+ </view>
|
|
|
+ <text class="score-value">{{ scoreValues[2] }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 长按/点击浮标弹出的个人菜单 -->
|
|
|
- <view class="action-overlay" v-if="showActionMenu" @tap="closeMenu"></view>
|
|
|
- <view class="action-sheet" v-if="showActionMenu">
|
|
|
- <view class="action-header">
|
|
|
- <view class="action-avatar-wrap">
|
|
|
- <image class="action-avatar" :src="actionMember ? (actionMember.avatar || defaultAvatar) : (currentMember.avatar || defaultAvatar)" mode="aspectFill" />
|
|
|
+ <view class="action-divider"></view>
|
|
|
+ <view class="action-item" @click="addMember" v-if="actionMember && actionMember.isSelf">
|
|
|
+ <text class="action-item-icon">➕</text>
|
|
|
+ <text class="action-item-text">添加成员</text>
|
|
|
</view>
|
|
|
- <view class="action-info">
|
|
|
- <text class="action-title">{{ actionMember ? actionMember.nickname : (currentMember.nickname || '我') }}</text>
|
|
|
- <text class="action-relation">{{ actionMember ? (actionMember.relationship || actionMember.relativeLabel || '') : '' }}</text>
|
|
|
+ <view class="action-item" @click="inviteMember" v-if="actionMember && actionMember.isSelf">
|
|
|
+ <text class="action-item-icon">📨</text>
|
|
|
+ <text class="action-item-text">邀请成员</text>
|
|
|
+ </view>
|
|
|
+ <view class="action-item" @click="switchToMember" v-if="canSwitch">
|
|
|
+ <text class="action-item-icon">🔄</text>
|
|
|
+ <text class="action-item-text">切换到该成员视角</text>
|
|
|
+ </view>
|
|
|
+ <view class="action-item" @click="editMember" v-if="canEdit">
|
|
|
+ <text class="action-item-icon">✏️</text>
|
|
|
+ <text class="action-item-text">编辑资料</text>
|
|
|
+ </view>
|
|
|
+ <view class="action-item" @click="questionnaireMember">
|
|
|
+ <text class="action-item-icon">📋</text>
|
|
|
+ <text class="action-item-text">关系问卷</text>
|
|
|
+ <text class="action-time" v-if="lastQuestionnaireText"> · 上次填写 {{ lastQuestionnaireText }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="action-item" @click="interactionMember">
|
|
|
+ <text class="action-item-icon">💬</text>
|
|
|
+ <text class="action-item-text">记录互动</text>
|
|
|
+ </view>
|
|
|
+ <view class="action-item" @click="callMember" v-if="actionMember && actionMember.phone">
|
|
|
+ <text class="action-item-icon">📞</text>
|
|
|
+ <text class="action-item-text">拨打电话</text>
|
|
|
+ </view>
|
|
|
+ <view class="action-item action-danger" @click="removeMember" v-if="canRemove">
|
|
|
+ <text class="action-item-icon">🗑️</text>
|
|
|
+ <text class="action-item-text">移除成员</text>
|
|
|
+ </view>
|
|
|
+ <view class="action-item action-cancel" @click="closeMenu">
|
|
|
+ <text class="action-item-text">取消</text>
|
|
|
</view>
|
|
|
- </view>
|
|
|
- <view class="action-item" @tap="switchToMember" v-if="canSwitch">
|
|
|
- <text class="action-item-icon">🔄</text>
|
|
|
- <text class="action-item-text">切换到该成员视角</text>
|
|
|
- </view>
|
|
|
- <view class="action-item" @tap="editMember" v-if="canEdit">
|
|
|
- <text class="action-item-icon">✏️</text>
|
|
|
- <text class="action-item-text">编辑资料</text>
|
|
|
- </view>
|
|
|
- <view class="action-item" @tap="questionnaireMember">
|
|
|
- <text class="action-item-icon">📋</text>
|
|
|
- <text class="action-item-text">关系问卷</text>
|
|
|
- </view>
|
|
|
- <view class="action-item" @tap="interactionMember">
|
|
|
- <text class="action-item-icon">💬</text>
|
|
|
- <text class="action-item-text">记录互动</text>
|
|
|
- </view>
|
|
|
- <view class="action-item" @tap="callMember" v-if="actionMember && actionMember.phone">
|
|
|
- <text class="action-item-icon">📞</text>
|
|
|
- <text class="action-item-text">拨打电话</text>
|
|
|
- </view>
|
|
|
- <view class="action-item action-danger" @tap="removeMember" v-if="canRemove">
|
|
|
- <text class="action-item-icon">🗑️</text>
|
|
|
- <text class="action-item-text">移除成员</text>
|
|
|
- </view>
|
|
|
- <view class="action-item action-cancel" @tap="closeMenu">
|
|
|
- <text class="action-item-text">取消</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 待审批请求弹窗 -->
|
|
|
- <view class="request-overlay" v-if="showRequests" @tap="closeRequests"></view>
|
|
|
- <view class="request-sheet" v-if="showRequests">
|
|
|
- <view class="sheet-header">
|
|
|
- <text class="sheet-title">待处理加入申请({{ pendingRequests.length }})</text>
|
|
|
- <text class="sheet-close" @tap="closeRequests">×</text>
|
|
|
- </view>
|
|
|
- <view v-if="pendingRequests.length === 0" class="empty-requests">
|
|
|
- <text>暂无待处理的请求</text>
|
|
|
- </view>
|
|
|
- <view class="request-item" v-for="req in pendingRequests" :key="req.id">
|
|
|
- <image class="req-avatar" src="/static/default-avatar.png" mode="aspectFill"></image>
|
|
|
- <view class="req-info">
|
|
|
- <text class="req-name">{{ req.requesterNickname }}</text>
|
|
|
- <text class="req-time">{{ req.createdAt }}</text>
|
|
|
- </view>
|
|
|
- <view class="req-actions">
|
|
|
- <button class="req-btn btn-approve" @tap="approveRequest(req.id)">通过</button>
|
|
|
- <button class="req-btn btn-reject" @tap="rejectRequest(req.id)">拒绝</button>
|
|
|
- </view>
|
|
|
+ <!-- 未创建家庭引导 -->
|
|
|
+ <view class="family-strip-empty" v-else>
|
|
|
+ <view class="empty-content">
|
|
|
+ <text class="empty-icon">🏠</text>
|
|
|
+ <text class="empty-title">还未创建家庭</text>
|
|
|
+ <text class="empty-desc">创建家庭,开启全家幸福之旅</text>
|
|
|
+ <view class="empty-btn" @click="onCreateFamily">+ 创建家庭</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
+
|
|
|
+ <!-- 邀请成员分享海报弹窗 -->
|
|
|
+ <SharePoster
|
|
|
+ :show="showPoster"
|
|
|
+ :referralCode="inviteCode"
|
|
|
+ :qrCodeBase64="inviteQrCode"
|
|
|
+ :nickname="posterNickname"
|
|
|
+ :userAvatar="posterAvatar"
|
|
|
+ @close="showPoster = false" />
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { kickFamilyMemberById, getPendingFamilyRequests, approveFamilyJoinRequest, rejectFamilyJoinRequest } from '@/utils/api'
|
|
|
+import FamilyMemberCard from '@/components/FamilyMemberCard'
|
|
|
+import SharePoster from '@/components/SharePoster.vue'
|
|
|
+import { kickFamilyMemberById, createFamily, generateFamilyInviteQrCode, getPairwiseRelationships } from '@/utils/api'
|
|
|
+import { parseDate } from '@/utils/format.js'
|
|
|
|
|
|
export default {
|
|
|
+ components: { FamilyMemberCard, SharePoster },
|
|
|
props: {
|
|
|
members: { type: Array, default: function() { return [] } },
|
|
|
selectedMemberId: { type: Number, default: null },
|
|
|
energyMap: { type: Object, default: function() { return {} } },
|
|
|
isParent: { type: Boolean, default: false },
|
|
|
- isAdmin: { type: Boolean, default: false }
|
|
|
+ centerMemberId: { type: Number, default: null }
|
|
|
},
|
|
|
data: function() {
|
|
|
return {
|
|
|
- fanOpen: false,
|
|
|
showActionMenu: false,
|
|
|
actionMember: null,
|
|
|
- pendingCount: 0,
|
|
|
- pendingRequests: [],
|
|
|
- showRequests: false
|
|
|
+ renderedMembers: [],
|
|
|
+ creatingFamily: false,
|
|
|
+ showPoster: false,
|
|
|
+ inviteCode: '',
|
|
|
+ inviteQrCode: '',
|
|
|
+ posterNickname: '',
|
|
|
+ posterAvatar: ''
|
|
|
}
|
|
|
},
|
|
|
- mounted: function() {
|
|
|
- this.loadPendingRequests()
|
|
|
- },
|
|
|
- computed: {
|
|
|
- defaultAvatar: function() {
|
|
|
- return '/static/default-avatar.png'
|
|
|
- },
|
|
|
- currentMember: function() {
|
|
|
- if (!this.members || this.members.length === 0) return {}
|
|
|
- for (var i = 0; i < this.members.length; i++) {
|
|
|
- if (this.members[i].id === this.selectedMemberId) return this.members[i]
|
|
|
- }
|
|
|
- return this.members[0]
|
|
|
+ watch: {
|
|
|
+ members: {
|
|
|
+ handler: function(val) {
|
|
|
+ this.renderedMembers = val || []
|
|
|
+ },
|
|
|
+ immediate: true
|
|
|
},
|
|
|
+ centerMemberId: {
|
|
|
+ handler: function(val) {
|
|
|
+ if (val) {
|
|
|
+ this.loadPairwise(val)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ immediate: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
canSwitch: function() {
|
|
|
- var target = this.actionMember || this.currentMember
|
|
|
- return target && target.userId && target.source === 'family_member'
|
|
|
+ // 仅家庭成员记录(source=family_member)且已关联账号时才能切换视角;
|
|
|
+ // 家长卡片(source=parent)是 users 表记录,switch 接口只查 family_members 表,切换会失败
|
|
|
+ return this.actionMember && this.actionMember.userId && this.actionMember.source === 'family_member'
|
|
|
},
|
|
|
canEdit: function() {
|
|
|
- var target = this.actionMember || this.currentMember
|
|
|
- return this.isParent && target && !target.userId && target.source === 'family_member'
|
|
|
+ return this.isParent && this.actionMember && !this.actionMember.userId && this.actionMember.source === 'family_member'
|
|
|
},
|
|
|
canRemove: function() {
|
|
|
- var target = this.actionMember || this.currentMember
|
|
|
- return this.isParent && target && !target.isSelf && target.source === 'family_member'
|
|
|
- }
|
|
|
- },
|
|
|
- methods: {
|
|
|
- // ===== 扇形展开 =====
|
|
|
- toggleFan: function(e) {
|
|
|
- if (this.fanOpen) {
|
|
|
- this.closeFan()
|
|
|
- } else {
|
|
|
- this.fanOpen = true
|
|
|
- }
|
|
|
+ // 仅家庭成员记录可移除(kick 接口只查 family_members 表),且不能移除自己
|
|
|
+ return this.isParent && this.actionMember && !this.actionMember.isSelf && this.actionMember.source === 'family_member'
|
|
|
},
|
|
|
- closeFan: function() {
|
|
|
- this.fanOpen = false
|
|
|
- },
|
|
|
- // 计算扇形位置: 头像在左边,成员向右扇形排列
|
|
|
- // 用固定的偏移量数组,避免用 transform-origin 的兼容问题
|
|
|
- getFanPosition: function(member, index) {
|
|
|
- if (!this.fanOpen) return 'transform: translate(0rpx, 0rpx) rotate(0deg);'
|
|
|
- var total = this.members.length
|
|
|
- var angleRange = 50
|
|
|
- var steps = Math.max(total - 1, 1)
|
|
|
- var angle = -angleRange + (index / steps) * angleRange * 2
|
|
|
- var rad = angle * Math.PI / 180
|
|
|
- var radius = 220
|
|
|
- var dx = Math.cos(rad) * radius
|
|
|
- var dy = Math.sin(rad) * radius
|
|
|
- var rotation = angle * 0.3
|
|
|
- return 'transform: translate(' + dx + 'rpx,' + dy + 'rpx) rotate(' + rotation + 'deg);'
|
|
|
+ // 关系现状三项宽度/数值(下标 0=信任度 1=亲密度 2=沟通质量)
|
|
|
+ actionScores: function() {
|
|
|
+ var m = this.actionMember
|
|
|
+ if (!m) return []
|
|
|
+ var keys = ['trustScore', 'intimacyScore', 'communicationScore']
|
|
|
+ return keys.map(function(k) {
|
|
|
+ var v = Number(m[k]) || 0
|
|
|
+ return { width: Math.max(0, Math.min(100, v)) + '%', value: Math.round(v) }
|
|
|
+ })
|
|
|
},
|
|
|
- getMemberEnergy: function(member) {
|
|
|
- if (!member || !this.energyMap) return null
|
|
|
- return this.energyMap[member.id] || this.energyMap[member.memberId] || null
|
|
|
+ scoreWidths: function() {
|
|
|
+ return this.actionScores.map(function(s) { return s.width })
|
|
|
},
|
|
|
- getRelationColor: function(member) {
|
|
|
- var rel = member && member.relationship
|
|
|
- if (rel === '爸爸' || rel === '妈妈' || rel === '父亲' || rel === '母亲') return '#F97316'
|
|
|
- if (rel === '儿子' || rel === '女儿') return '#0EA5E9'
|
|
|
- if (rel === '爷爷' || rel === '奶奶' || rel === '外公' || rel === '外婆') return '#6B7280'
|
|
|
- if (rel === '哥哥' || rel === '姐姐' || rel === '弟弟' || rel === '妹妹') return '#10B981'
|
|
|
- if (member && member.isSpouse) return '#EC4899'
|
|
|
- var role = member && member.effectiveRole
|
|
|
- if (role === 'parent') return '#F97316'
|
|
|
- if (role === 'child') return '#0EA5E9'
|
|
|
- if (role === 'teacher') return '#8B5CF6'
|
|
|
- return '#9CA3AF'
|
|
|
+ scoreValues: function() {
|
|
|
+ return this.actionScores.map(function(s) { return s.value })
|
|
|
},
|
|
|
-
|
|
|
- // ===== 成员选择 =====
|
|
|
- selectMember: function(member) {
|
|
|
+ // 上次填写时间(问卷提交时 updateMemberScores 已更新 lastInteractionAt)
|
|
|
+ lastQuestionnaireText: function() {
|
|
|
+ var m = this.actionMember
|
|
|
+ if (!m || !m.lastInteractionAt) return ''
|
|
|
+ var d = parseDate(m.lastInteractionAt)
|
|
|
+ if (!d) return ''
|
|
|
+ var now = new Date()
|
|
|
+ var diffMs = now.getTime() - d.getTime()
|
|
|
+ var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
|
|
|
+ if (diffDays === 0) return '今天'
|
|
|
+ if (diffDays === 1) return '昨天'
|
|
|
+ if (diffDays < 7) return diffDays + '天前'
|
|
|
+ if (diffDays < 30) return Math.floor(diffDays / 7) + '周前'
|
|
|
+ return Math.floor(diffDays / 30) + '个月前'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ onSelect: function(member) {
|
|
|
this.$emit('select', member)
|
|
|
- // 关闭扇形
|
|
|
- this.fanOpen = false
|
|
|
- },
|
|
|
-
|
|
|
- // ===== 个人菜单 =====
|
|
|
- onFanLongPress: function(member) {
|
|
|
- this.actionMember = member
|
|
|
- this.showActionMenu = true
|
|
|
},
|
|
|
onLongPress: function(member) {
|
|
|
this.actionMember = member
|
|
|
@@ -231,39 +231,35 @@ export default {
|
|
|
this.actionMember = null
|
|
|
},
|
|
|
switchToMember: function() {
|
|
|
- var member = this.actionMember || this.currentMember
|
|
|
+ var member = this.actionMember
|
|
|
this.closeMenu()
|
|
|
if (member && member.userId) {
|
|
|
this.$emit('switch', member)
|
|
|
}
|
|
|
},
|
|
|
editMember: function() {
|
|
|
- var member = this.actionMember || this.currentMember
|
|
|
+ var member = this.actionMember
|
|
|
this.closeMenu()
|
|
|
if (member) {
|
|
|
- var params = 'memberId=' + member.id + '&nickname=' + encodeURIComponent(member.nickname || '')
|
|
|
- if (member.gender) params += '&gender=' + member.gender
|
|
|
- if (member.phone) params += '&phone=' + member.phone
|
|
|
- if (member.birthday) params += '&birthday=' + member.birthday
|
|
|
- uni.navigateTo({ url: '/pages/family/add-member?' + params })
|
|
|
+ uni.navigateTo({ url: '/pages/family/add-member?memberId=' + member.id })
|
|
|
}
|
|
|
},
|
|
|
questionnaireMember: function() {
|
|
|
- var member = this.actionMember || this.currentMember
|
|
|
+ var member = this.actionMember
|
|
|
this.closeMenu()
|
|
|
if (member) {
|
|
|
uni.navigateTo({ url: '/pages/action-detail/relationship-questionnaire?memberId=' + member.id })
|
|
|
}
|
|
|
},
|
|
|
interactionMember: function() {
|
|
|
- var member = this.actionMember || this.currentMember
|
|
|
+ var member = this.actionMember
|
|
|
this.closeMenu()
|
|
|
if (member) {
|
|
|
uni.navigateTo({ url: '/pages/action-detail/interaction-log?memberId=' + member.id })
|
|
|
}
|
|
|
},
|
|
|
callMember: function() {
|
|
|
- var member = this.actionMember || this.currentMember
|
|
|
+ var member = this.actionMember
|
|
|
this.closeMenu()
|
|
|
if (member && member.phone) {
|
|
|
uni.makePhoneCall({ phoneNumber: member.phone })
|
|
|
@@ -271,7 +267,7 @@ export default {
|
|
|
},
|
|
|
removeMember: function() {
|
|
|
var self = this
|
|
|
- var member = this.actionMember || this.currentMember
|
|
|
+ var member = this.actionMember
|
|
|
this.closeMenu()
|
|
|
if (member) {
|
|
|
uni.showModal({
|
|
|
@@ -284,6 +280,7 @@ export default {
|
|
|
.then(function() {
|
|
|
uni.hideLoading()
|
|
|
uni.showToast({ title: '已移除', icon: 'success' })
|
|
|
+ // 通知父组件刷新成员列表(替代无效的 globalData 标志)
|
|
|
self.$emit('removed', member)
|
|
|
})
|
|
|
.catch(function(err) {
|
|
|
@@ -295,350 +292,108 @@ export default {
|
|
|
})
|
|
|
}
|
|
|
},
|
|
|
-
|
|
|
- // ===== 待审批请求 =====
|
|
|
- loadPendingRequests: function() {
|
|
|
+ getMemberEnergy: function(member) {
|
|
|
+ if (!member || !this.energyMap) return null
|
|
|
+ return this.energyMap[member.id] || this.energyMap[member.memberId] || null
|
|
|
+ },
|
|
|
+ onCreateFamily: function() {
|
|
|
var self = this
|
|
|
- if (!this.isAdmin) return
|
|
|
- getPendingFamilyRequests()
|
|
|
+ if (this.creatingFamily) return
|
|
|
+ this.creatingFamily = true
|
|
|
+ // 家庭名称使用当前用户昵称 + '的家庭'(与 user-edit.vue createFamilyAction 一致)
|
|
|
+ var userInfo = uni.getStorageSync('userInfo')
|
|
|
+ var nickname = uni.getStorageSync('nickname') || (userInfo && userInfo.nickname) || '我的'
|
|
|
+ uni.showLoading({ title: '创建中...' })
|
|
|
+ createFamily(nickname + '的家庭')
|
|
|
.then(function(res) {
|
|
|
+ uni.hideLoading()
|
|
|
+ self.creatingFamily = false
|
|
|
if (res.code === 200) {
|
|
|
- var data = res.data || []
|
|
|
- self.pendingCount = data.length
|
|
|
- self.pendingRequests = data
|
|
|
+ uni.showToast({ title: '家庭创建成功', icon: 'success' })
|
|
|
+ self.$emit('created')
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '创建失败', icon: 'none' })
|
|
|
}
|
|
|
})
|
|
|
- .catch(function() {
|
|
|
- // 静默失败
|
|
|
+ .catch(function(err) {
|
|
|
+ uni.hideLoading()
|
|
|
+ self.creatingFamily = false
|
|
|
+ uni.showToast({ title: (err && err.message) || '创建失败', icon: 'none' })
|
|
|
})
|
|
|
},
|
|
|
- openRequests: function() {
|
|
|
- this.showRequests = true
|
|
|
- },
|
|
|
- closeRequests: function() {
|
|
|
- this.showRequests = false
|
|
|
+ addMember: function() {
|
|
|
+ this.closeMenu()
|
|
|
+ uni.navigateTo({ url: '/pages/family/add-member' })
|
|
|
},
|
|
|
- approveRequest: function(requestId) {
|
|
|
+ // 邀请成员:弹窗展示分享海报(与 family-members.vue onInvite 一致的模式)
|
|
|
+ inviteMember: function() {
|
|
|
var self = this
|
|
|
- uni.showModal({
|
|
|
- title: '确认通过',
|
|
|
- content: '确认通过该成员的加入申请?',
|
|
|
- success: function(modalRes) {
|
|
|
- if (modalRes.confirm) {
|
|
|
- approveFamilyJoinRequest(requestId)
|
|
|
- .then(function() {
|
|
|
- uni.showToast({ title: '已通过', icon: 'success' })
|
|
|
- self.loadPendingRequests()
|
|
|
- self.closeRequests()
|
|
|
- })
|
|
|
- .catch(function(err) {
|
|
|
- uni.showToast({ title: err.message || '操作失败', icon: 'none' })
|
|
|
- })
|
|
|
+ this.closeMenu()
|
|
|
+ var userInfo = uni.getStorageSync('userInfo')
|
|
|
+ this.posterNickname = uni.getStorageSync('nickname') || (userInfo && userInfo.nickname) || ''
|
|
|
+ this.posterAvatar = uni.getStorageSync('avatar') || (userInfo && userInfo.avatar) || ''
|
|
|
+ this.inviteCode = ''
|
|
|
+ this.inviteQrCode = ''
|
|
|
+ uni.showLoading({ title: '生成邀请...' })
|
|
|
+ generateFamilyInviteQrCode()
|
|
|
+ .then(function(res) {
|
|
|
+ uni.hideLoading()
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ self.inviteCode = res.data.inviteCode
|
|
|
+ self.inviteQrCode = res.data.qrCodeBase64
|
|
|
+ self.showPoster = true
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '生成失败', icon: 'none' })
|
|
|
}
|
|
|
- }
|
|
|
- })
|
|
|
+ })
|
|
|
+ .catch(function() {
|
|
|
+ uni.hideLoading()
|
|
|
+ uni.showToast({ title: '生成失败', icon: 'none' })
|
|
|
+ })
|
|
|
},
|
|
|
- rejectRequest: function(requestId) {
|
|
|
+ loadPairwise: function(centerMemberId) {
|
|
|
var self = this
|
|
|
- uni.showModal({
|
|
|
- title: '确认拒绝',
|
|
|
- content: '确认拒绝该成员的加入申请?',
|
|
|
- success: function(modalRes) {
|
|
|
- if (modalRes.confirm) {
|
|
|
- rejectFamilyJoinRequest(requestId)
|
|
|
- .then(function() {
|
|
|
- uni.showToast({ title: '已拒绝', icon: 'success' })
|
|
|
- self.loadPendingRequests()
|
|
|
- self.closeRequests()
|
|
|
- })
|
|
|
- .catch(function(err) {
|
|
|
- uni.showToast({ title: err.message || '操作失败', icon: 'none' })
|
|
|
- })
|
|
|
- }
|
|
|
+ getPairwiseRelationships(centerMemberId).then(function(res) {
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ var arr = res.data instanceof Array ? res.data : ((res.data && res.data.list) || [])
|
|
|
+ self.renderedMembers = arr
|
|
|
}
|
|
|
- })
|
|
|
+ }).catch(function() {})
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.member-strip {
|
|
|
- /* FAB 悬浮在左侧边缘中部偏下;禁用 transform(transform 会创建 containing block,
|
|
|
- 使后续 .fan-overlay (position: fixed) 退化为相对本容器定位,全屏遮罩失效) */
|
|
|
- position: fixed;
|
|
|
- bottom: 45%;
|
|
|
- left: 16rpx;
|
|
|
- z-index: 100;
|
|
|
+.family-member-strip {
|
|
|
+ margin: 16rpx 0;
|
|
|
+ padding: 0 30rpx;
|
|
|
}
|
|
|
-
|
|
|
-/* ===== 浮标 ===== */
|
|
|
-.fab-wrap {
|
|
|
- position: relative;
|
|
|
- display: inline-block;
|
|
|
- margin: 4rpx 0;
|
|
|
- z-index: 1001;
|
|
|
-}
|
|
|
-.fab-avatar {
|
|
|
- width: 72rpx;
|
|
|
- height: 72rpx;
|
|
|
- border-radius: 50%;
|
|
|
- background: linear-gradient(135deg, #F97316, #FB923C);
|
|
|
- box-shadow: 0 6rpx 24rpx rgba(249, 115, 22, 0.35), 0 0 0 4rpx rgba(249, 115, 22, 0.12);
|
|
|
- position: relative;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.3s ease;
|
|
|
-}
|
|
|
-.fab-avatar:active {
|
|
|
- transform: scale(0.9);
|
|
|
-}
|
|
|
-.fab-avatar.active {
|
|
|
- transform: scale(1.05);
|
|
|
- box-shadow: 0 8rpx 32rpx rgba(249, 115, 22, 0.45), 0 0 0 6rpx rgba(249, 115, 22, 0.15);
|
|
|
-}
|
|
|
-
|
|
|
-/* 默认态:家庭图标 */
|
|
|
-.fab-icon-default {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-.fab-icon-emoji {
|
|
|
- font-size: 36rpx;
|
|
|
- line-height: 1;
|
|
|
- filter: drop-shadow(0 1rpx 2rpx rgba(0,0,0,0.1));
|
|
|
-}
|
|
|
-
|
|
|
-/* 展开态:当前成员头像(渐变出现) */
|
|
|
-.fab-img {
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
+.strip-scroll {
|
|
|
width: 100%;
|
|
|
- height: 100%;
|
|
|
- border-radius: 50%;
|
|
|
- opacity: 0;
|
|
|
- transform: scale(0.8);
|
|
|
- transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
|
-}
|
|
|
-.fab-img-show {
|
|
|
- opacity: 1;
|
|
|
- transform: scale(1);
|
|
|
-}
|
|
|
-
|
|
|
-/* 光环 */
|
|
|
-.fab-ring {
|
|
|
- position: absolute;
|
|
|
- top: -4rpx;
|
|
|
- left: -4rpx;
|
|
|
- right: -4rpx;
|
|
|
- bottom: -4rpx;
|
|
|
- border-radius: 50%;
|
|
|
- border: 3rpx solid rgba(255, 255, 255, 0.6);
|
|
|
- opacity: 0;
|
|
|
- transition: opacity 0.3s ease;
|
|
|
-}
|
|
|
-.fab-ring.ring-active {
|
|
|
- opacity: 1;
|
|
|
-}
|
|
|
-.fab-badge {
|
|
|
- position: absolute;
|
|
|
- top: -4rpx;
|
|
|
- right: -8rpx;
|
|
|
- width: 32rpx;
|
|
|
- height: 32rpx;
|
|
|
- border-radius: 50%;
|
|
|
- background: #EF4444;
|
|
|
- border: 2rpx solid #FFFFFF;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-.badge-count {
|
|
|
- font-size: 16rpx;
|
|
|
- color: #FFFFFF;
|
|
|
- font-weight: bold;
|
|
|
-}
|
|
|
-.fab-pulse {
|
|
|
- position: absolute;
|
|
|
- top: 50%;
|
|
|
- left: 50%;
|
|
|
- width: 72rpx;
|
|
|
- height: 72rpx;
|
|
|
- margin-top: -36rpx;
|
|
|
- margin-left: -36rpx;
|
|
|
- border-radius: 50%;
|
|
|
- background: rgba(249, 115, 22, 0.25);
|
|
|
- animation: fab-pulse-anim 2.5s ease-out infinite;
|
|
|
- /* 呼吸动画层不拦截点击,否则常驻覆盖 FAB 导致无法展开成员扇形 */
|
|
|
- pointer-events: none;
|
|
|
-}
|
|
|
-@keyframes fab-pulse-anim {
|
|
|
- 0% { transform: scale(1); opacity: 0.6; }
|
|
|
- 100% { transform: scale(2); opacity: 0; }
|
|
|
-}
|
|
|
-
|
|
|
-/* ===== 扇形展开 ===== */
|
|
|
-/* fan-wrap 用 fixed 覆盖整个视口,避免 absolute 套小容器导致位置错乱 */
|
|
|
-.fan-wrap {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- z-index: 999;
|
|
|
- pointer-events: none;
|
|
|
-}
|
|
|
-.fan-wrap.show {
|
|
|
- pointer-events: auto;
|
|
|
-}
|
|
|
-.fan-overlay {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- background: rgba(0, 0, 0, 0.25);
|
|
|
- opacity: 0;
|
|
|
- transition: opacity 0.3s ease;
|
|
|
- z-index: 999;
|
|
|
-}
|
|
|
-.fan-wrap.show .fan-overlay {
|
|
|
- opacity: 1;
|
|
|
-}
|
|
|
-/* fan-container 锚定到 FAB 坐标(FAB 在 left:16rpx, bottom:33.33%),零尺寸做原点 */
|
|
|
-.fan-container {
|
|
|
- position: absolute;
|
|
|
- bottom: 33.33%;
|
|
|
- left: 16rpx;
|
|
|
- top: auto;
|
|
|
- right: auto;
|
|
|
- width: 0;
|
|
|
- height: 0;
|
|
|
- padding: 0;
|
|
|
- overflow: visible;
|
|
|
-}
|
|
|
-
|
|
|
-.fan-card {
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: 110rpx;
|
|
|
- pointer-events: auto;
|
|
|
- transform: translate(0, 0) rotate(0deg);
|
|
|
- transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease;
|
|
|
- opacity: 0;
|
|
|
- z-index: 1000;
|
|
|
-}
|
|
|
-.fan-wrap.show .fan-card {
|
|
|
- opacity: 1;
|
|
|
-}
|
|
|
-.fan-card.selected {
|
|
|
- filter: brightness(0.95);
|
|
|
-}
|
|
|
-.fan-avatar-wrap {
|
|
|
- position: relative;
|
|
|
- width: 64rpx;
|
|
|
- height: 64rpx;
|
|
|
- margin: 0 auto;
|
|
|
-}
|
|
|
-.fan-avatar {
|
|
|
- width: 64rpx;
|
|
|
- height: 64rpx;
|
|
|
- border-radius: 50%;
|
|
|
- background: #FFFFFF;
|
|
|
- border: 2rpx solid #FFFFFF;
|
|
|
- box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
|
|
-}
|
|
|
-.fan-relation-badge {
|
|
|
- position: absolute;
|
|
|
- bottom: -4rpx;
|
|
|
- left: 50%;
|
|
|
- transform: translateX(-50%);
|
|
|
- padding: 1rpx 8rpx;
|
|
|
- border-radius: 10rpx;
|
|
|
white-space: nowrap;
|
|
|
- max-width: 76rpx;
|
|
|
}
|
|
|
-.fan-relation-text {
|
|
|
- font-size: 15rpx;
|
|
|
- color: #FFFFFF;
|
|
|
- font-weight: 500;
|
|
|
- line-height: 1.4;
|
|
|
-}
|
|
|
-.fan-spouse-mark {
|
|
|
- position: absolute;
|
|
|
- top: -2rpx;
|
|
|
- right: -2rpx;
|
|
|
- width: 22rpx;
|
|
|
- height: 22rpx;
|
|
|
+.strip-inner {
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
+ flex-direction: row;
|
|
|
}
|
|
|
-.spouse-icon {
|
|
|
- font-size: 16rpx;
|
|
|
-}
|
|
|
-.fan-nickname {
|
|
|
- display: block;
|
|
|
- margin-top: 4rpx;
|
|
|
- text-align: center;
|
|
|
- font-size: 20rpx;
|
|
|
- color: #FFFFFF;
|
|
|
- font-weight: 600;
|
|
|
- text-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.4);
|
|
|
- line-height: 1.2;
|
|
|
- max-width: 90rpx;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- white-space: nowrap;
|
|
|
-}
|
|
|
-.fan-score {
|
|
|
- display: flex;
|
|
|
- align-items: baseline;
|
|
|
- justify-content: center;
|
|
|
- margin-top: 2rpx;
|
|
|
+.strip-inner.scroll-mode {
|
|
|
+ gap: 16rpx;
|
|
|
}
|
|
|
-.score-value {
|
|
|
- font-size: 18rpx;
|
|
|
- color: #FBBF24;
|
|
|
- font-weight: bold;
|
|
|
- text-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.4);
|
|
|
-}
|
|
|
-.score-unit {
|
|
|
- font-size: 15rpx;
|
|
|
- color: rgba(255, 255, 255, 0.8);
|
|
|
- margin-left: 2rpx;
|
|
|
- text-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.4);
|
|
|
-}
|
|
|
-.fan-selected-mark {
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- right: 0;
|
|
|
- width: 22rpx;
|
|
|
- height: 22rpx;
|
|
|
- border-radius: 50%;
|
|
|
- background: #F97316;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
+.strip-inner.center-mode {
|
|
|
justify-content: center;
|
|
|
+ gap: 24rpx;
|
|
|
+ flex-wrap: wrap;
|
|
|
}
|
|
|
-.mark-icon {
|
|
|
- font-size: 14rpx;
|
|
|
- color: #FFFFFF;
|
|
|
- font-weight: bold;
|
|
|
+.strip-scroll .strip-inner.scroll-mode {
|
|
|
+ display: inline-flex;
|
|
|
}
|
|
|
-
|
|
|
-/* ===== 操作菜单 ===== */
|
|
|
+/* 操作菜单 */
|
|
|
.action-overlay {
|
|
|
position: fixed;
|
|
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
|
background: rgba(0,0,0,0.4);
|
|
|
- z-index: 1001;
|
|
|
+ z-index: 999;
|
|
|
}
|
|
|
.action-sheet {
|
|
|
position: fixed;
|
|
|
@@ -647,63 +402,35 @@ export default {
|
|
|
right: 0;
|
|
|
background: #FFFFFF;
|
|
|
border-radius: 24rpx 24rpx 0 0;
|
|
|
- z-index: 1002;
|
|
|
+ z-index: 1000;
|
|
|
padding: 20rpx 0;
|
|
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|
|
- animation: sheet-up 0.3s ease;
|
|
|
-}
|
|
|
-@keyframes sheet-up {
|
|
|
- from { transform: translateY(100%); }
|
|
|
- to { transform: translateY(0); }
|
|
|
-}
|
|
|
-.action-header {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 16rpx 30rpx 20rpx;
|
|
|
- border-bottom: 1rpx solid #F3F4F6;
|
|
|
- gap: 16rpx;
|
|
|
-}
|
|
|
-.action-avatar-wrap {
|
|
|
- width: 56rpx;
|
|
|
- height: 56rpx;
|
|
|
- border-radius: 50%;
|
|
|
- overflow: hidden;
|
|
|
- flex-shrink: 0;
|
|
|
-}
|
|
|
-.action-avatar {
|
|
|
- width: 56rpx;
|
|
|
- height: 56rpx;
|
|
|
- border-radius: 50%;
|
|
|
-}
|
|
|
-.action-info {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- gap: 2rpx;
|
|
|
}
|
|
|
.action-title {
|
|
|
- font-size: 28rpx;
|
|
|
+ font-size: 30rpx;
|
|
|
font-weight: 600;
|
|
|
color: #1F2937;
|
|
|
}
|
|
|
.action-relation {
|
|
|
- font-size: 22rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
color: #9CA3AF;
|
|
|
+ margin-top: 4rpx;
|
|
|
}
|
|
|
.action-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- padding: 22rpx 30rpx;
|
|
|
+ padding: 24rpx 30rpx;
|
|
|
border-bottom: 1rpx solid #F9FAFB;
|
|
|
}
|
|
|
.action-item:active {
|
|
|
background: #F3F4F6;
|
|
|
}
|
|
|
.action-item-icon {
|
|
|
- font-size: 30rpx;
|
|
|
+ font-size: 32rpx;
|
|
|
margin-right: 16rpx;
|
|
|
}
|
|
|
.action-item-text {
|
|
|
- font-size: 26rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
color: #374151;
|
|
|
}
|
|
|
.action-danger .action-item-text {
|
|
|
@@ -711,212 +438,100 @@ export default {
|
|
|
}
|
|
|
.action-cancel {
|
|
|
justify-content: center;
|
|
|
- margin-top: 8rpx;
|
|
|
+ margin-top: 10rpx;
|
|
|
border-bottom: none;
|
|
|
}
|
|
|
.action-cancel .action-item-text {
|
|
|
color: #6B7280;
|
|
|
}
|
|
|
-
|
|
|
-/* ===== 待审批请求 ===== */
|
|
|
-.request-overlay {
|
|
|
- position: fixed;
|
|
|
- top: 0; left: 0; right: 0; bottom: 0;
|
|
|
- background: rgba(0,0,0,0.4);
|
|
|
- z-index: 1003;
|
|
|
+.action-time {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #9CA3AF;
|
|
|
+ margin-left: 4rpx;
|
|
|
}
|
|
|
-.request-sheet {
|
|
|
- position: fixed;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
+/* 空状态 */
|
|
|
+.family-strip-empty {
|
|
|
+ margin: 16rpx 0;
|
|
|
+ padding: 40rpx 30rpx;
|
|
|
background: #FFFFFF;
|
|
|
- border-radius: 24rpx 24rpx 0 0;
|
|
|
- z-index: 1004;
|
|
|
- padding: 24rpx 0;
|
|
|
- padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
|
- animation: sheet-up 0.3s ease;
|
|
|
- max-height: 60vh;
|
|
|
- overflow-y: auto;
|
|
|
-}
|
|
|
-.sheet-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- padding: 0 30rpx 16rpx;
|
|
|
- border-bottom: 1rpx solid #F3F4F6;
|
|
|
-}
|
|
|
-.sheet-title {
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: 600;
|
|
|
- color: #1F2937;
|
|
|
-}
|
|
|
-.sheet-close {
|
|
|
- font-size: 32rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-.empty-requests {
|
|
|
- padding: 60rpx 0;
|
|
|
- text-align: center;
|
|
|
- font-size: 24rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-.request-item {
|
|
|
+ border-radius: 24rpx;
|
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 20rpx 30rpx;
|
|
|
- gap: 16rpx;
|
|
|
-}
|
|
|
-.req-avatar {
|
|
|
- width: 56rpx;
|
|
|
- height: 56rpx;
|
|
|
- border-radius: 50%;
|
|
|
- flex-shrink: 0;
|
|
|
+ justify-content: center;
|
|
|
}
|
|
|
-.req-info {
|
|
|
- flex: 1;
|
|
|
+.empty-content {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- gap: 2rpx;
|
|
|
-}
|
|
|
-.req-name {
|
|
|
- font-size: 26rpx;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-.req-time {
|
|
|
- font-size: 20rpx;
|
|
|
- color: #999;
|
|
|
-}
|
|
|
-.req-actions {
|
|
|
- display: flex;
|
|
|
- gap: 12rpx;
|
|
|
-}
|
|
|
-.req-btn {
|
|
|
- padding: 8rpx 20rpx;
|
|
|
- border-radius: 20rpx;
|
|
|
- font-size: 22rpx;
|
|
|
- font-weight: 500;
|
|
|
+ align-items: center;
|
|
|
}
|
|
|
-.btn-approve {
|
|
|
- background: #10B981;
|
|
|
+.empty-icon { font-size: 56rpx; }
|
|
|
+.empty-title { font-size: 30rpx; font-weight: 600; color: #1F2937; margin-top: 12rpx; }
|
|
|
+.empty-desc { font-size: 24rpx; color: #9CA3AF; margin-top: 8rpx; }
|
|
|
+.empty-btn {
|
|
|
+ margin-top: 24rpx;
|
|
|
+ padding: 16rpx 48rpx;
|
|
|
+ background: #F97316;
|
|
|
color: #FFFFFF;
|
|
|
+ font-size: 28rpx;
|
|
|
+ border-radius: 40rpx;
|
|
|
}
|
|
|
-.btn-reject {
|
|
|
- background: #F3F4F6;
|
|
|
- color: #666;
|
|
|
-}
|
|
|
-</style>
|
|
|
-
|
|
|
-.badge-count {
|
|
|
- font-size: 26rpx;
|
|
|
- font-weight: 700;
|
|
|
- color: #EF4444;
|
|
|
-}
|
|
|
-
|
|
|
-/* 待审批请求弹窗 */
|
|
|
-.request-overlay {
|
|
|
- position: fixed;
|
|
|
- top: 0; left: 0; right: 0; bottom: 0;
|
|
|
- background: rgba(0,0,0,0.4);
|
|
|
- z-index: 1500;
|
|
|
-}
|
|
|
-
|
|
|
-.request-sheet {
|
|
|
- position: fixed;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- background: #FFFFFF;
|
|
|
- border-radius: 24rpx 24rpx 0 0;
|
|
|
- z-index: 1501;
|
|
|
- max-height: 70vh;
|
|
|
- overflow-y: auto;
|
|
|
- padding: 20rpx 0;
|
|
|
- padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|
|
-}
|
|
|
-
|
|
|
-.sheet-header {
|
|
|
+/* Action Sheet 头部(覆盖原有 column 布局) */
|
|
|
+.action-header {
|
|
|
display: flex;
|
|
|
- justify-content: space-between;
|
|
|
+ flex-direction: row;
|
|
|
align-items: center;
|
|
|
- padding: 16rpx 30rpx 16rpx;
|
|
|
+ padding: 24rpx 30rpx;
|
|
|
border-bottom: 1rpx solid #F3F4F6;
|
|
|
}
|
|
|
-
|
|
|
-.sheet-title {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: 700;
|
|
|
- color: #1F2937;
|
|
|
+.action-avatar {
|
|
|
+ width: 88rpx;
|
|
|
+ height: 88rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-right: 20rpx;
|
|
|
+ background: #F3F4F6;
|
|
|
}
|
|
|
-
|
|
|
-.sheet-close {
|
|
|
- font-size: 40rpx;
|
|
|
- color: #9CA3AF;
|
|
|
- padding: 8rpx;
|
|
|
- line-height: 1;
|
|
|
+.action-header-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
}
|
|
|
-
|
|
|
-.empty-requests {
|
|
|
- text-align: center;
|
|
|
- padding: 60rpx 0;
|
|
|
- font-size: 28rpx;
|
|
|
- color: #9CA3AF;
|
|
|
+/* 关系现状 */
|
|
|
+.action-scores {
|
|
|
+ padding: 20rpx 30rpx;
|
|
|
}
|
|
|
-
|
|
|
-.request-item {
|
|
|
+.score-row {
|
|
|
display: flex;
|
|
|
+ flex-direction: row;
|
|
|
align-items: center;
|
|
|
- padding: 20rpx 30rpx;
|
|
|
- border-bottom: 1rpx solid #F9FAFB;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
}
|
|
|
-
|
|
|
-.req-avatar {
|
|
|
- width: 80rpx;
|
|
|
- height: 80rpx;
|
|
|
- border-radius: 50%;
|
|
|
- margin-right: 16rpx;
|
|
|
- flex-shrink: 0;
|
|
|
+.score-label {
|
|
|
+ width: 110rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #6B7280;
|
|
|
}
|
|
|
-
|
|
|
-.req-info {
|
|
|
+.score-bar-track {
|
|
|
flex: 1;
|
|
|
- min-width: 0;
|
|
|
-}
|
|
|
-
|
|
|
-.req-name {
|
|
|
- font-size: 30rpx;
|
|
|
- font-weight: 600;
|
|
|
- color: #1F2937;
|
|
|
- display: block;
|
|
|
-}
|
|
|
-
|
|
|
-.req-time {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #9CA3AF;
|
|
|
- display: block;
|
|
|
- margin-top: 4rpx;
|
|
|
+ height: 14rpx;
|
|
|
+ background: #F3F4F6;
|
|
|
+ border-radius: 7rpx;
|
|
|
+ overflow: hidden;
|
|
|
}
|
|
|
-
|
|
|
-.req-actions {
|
|
|
- display: flex;
|
|
|
- gap: 12rpx;
|
|
|
+.score-bar-fill {
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 7rpx;
|
|
|
}
|
|
|
-
|
|
|
-.req-btn {
|
|
|
- padding: 12rpx 24rpx;
|
|
|
- border-radius: 8rpx;
|
|
|
+.score-bar-fill.trust { background: #10B981; }
|
|
|
+.score-bar-fill.intimacy { background: #3B82F6; }
|
|
|
+.score-bar-fill.communication { background: #F59E0B; }
|
|
|
+.score-value {
|
|
|
+ width: 70rpx;
|
|
|
+ text-align: right;
|
|
|
font-size: 24rpx;
|
|
|
+ color: #1F2937;
|
|
|
font-weight: 600;
|
|
|
- border: none;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-approve {
|
|
|
- background: #16A34A;
|
|
|
- color: #FFFFFF;
|
|
|
}
|
|
|
-
|
|
|
-.btn-reject {
|
|
|
- background: #EF4444;
|
|
|
- color: #FFFFFF;
|
|
|
+.action-divider {
|
|
|
+ height: 1rpx;
|
|
|
+ background: #F3F4F6;
|
|
|
+ margin: 0 30rpx;
|
|
|
}
|
|
|
</style>
|