|
|
@@ -0,0 +1,290 @@
|
|
|
+<template>
|
|
|
+ <!--
|
|
|
+ 五维页 · 成员标签条(DimensionMemberTabs)
|
|
|
+ 用途:已登录家庭用户在身/心/智/富页通过点击标签切换成员,查看该成员各维度数值。
|
|
|
+ 视觉:chip 形态(比大卡更轻,适合作为 PageBanner 下的筛选器);选中态用所属维度色渐变。
|
|
|
+ 交互:点击切换(emit select);长按唤出管理菜单(emit longpress);右侧管理入口(emit manage)。
|
|
|
+ 规范:触摸目标 >= 88rpx;维度色取 uni.scss 令牌(编译期内联,规避 mp-weixin CSS var 不稳)。
|
|
|
+ -->
|
|
|
+ <view class="member-tabs" :class="'dim-' + (dimensionCode || 'body')">
|
|
|
+ <scroll-view
|
|
|
+ class="tabs-scroll"
|
|
|
+ scroll-x
|
|
|
+ :enhanced="true"
|
|
|
+ :scroll-with-animation="true"
|
|
|
+ show-scrollbar="false"
|
|
|
+ v-if="members && members.length > 0"
|
|
|
+ >
|
|
|
+ <view class="tabs-inner">
|
|
|
+ <view
|
|
|
+ v-for="m in members"
|
|
|
+ :key="m.id"
|
|
|
+ class="member-tab"
|
|
|
+ :class="{ 'is-selected': String(m.id) === String(selectedMemberId), 'is-self': m.isSelf }"
|
|
|
+ @click="onTap(m)"
|
|
|
+ @longpress="onLongPress(m)"
|
|
|
+ >
|
|
|
+ <view class="tab-avatar-wrap">
|
|
|
+ <image class="tab-avatar" :src="m.avatar || defaultAvatar" mode="aspectFill" />
|
|
|
+ <view class="tab-self-mark" v-if="m.isSelf">我</view>
|
|
|
+ </view>
|
|
|
+ <view class="tab-body">
|
|
|
+ <text class="tab-name">{{ m.nickname || '成员' }}</text>
|
|
|
+ <view class="tab-meta">
|
|
|
+ <text class="tab-relation">{{ relationText(m) }}</text>
|
|
|
+ <text class="tab-score" v-if="scoreOf(m) !== null">{{ scoreOf(m) }}<text class="tab-score-unit">分</text></text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 管理入口 -->
|
|
|
+ <view class="tab-manage" @click="onManage" v-if="members.length > 1">
|
|
|
+ <text class="tab-manage-icon">管理</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+
|
|
|
+ <!-- 空态:未创建家庭 / 无可见成员 -->
|
|
|
+ <view class="tabs-empty" v-else>
|
|
|
+ <text class="tabs-empty-text">创建家庭后,可在这里切换查看每位家人的维度数值</text>
|
|
|
+ <view class="tabs-empty-btn" @click="onManage">+ 管理家庭成员</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'DimensionMemberTabs',
|
|
|
+ props: {
|
|
|
+ members: { type: Array, default: function() { return [] } },
|
|
|
+ selectedMemberId: { type: [Number, String], default: null },
|
|
|
+ // 所属维度码:body / mind / wisdom / wealth / action,决定选中态配色
|
|
|
+ dimensionCode: { type: String, default: 'body' },
|
|
|
+ // { memberId: score },用于在标签上直接显示该成员的维度分数
|
|
|
+ scoreMap: { type: Object, default: function() { return {} } }
|
|
|
+ },
|
|
|
+ data: function() {
|
|
|
+ return { defaultAvatar: '/static/default-avatar.png' }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ dimensionList: function() {
|
|
|
+ return this.members || []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ relationText: function(m) {
|
|
|
+ if (!m) return '成员'
|
|
|
+ return m.relationship || m.relativeLabel || m.roleLabel || (m.isSelf ? '本人' : '成员')
|
|
|
+ },
|
|
|
+ scoreOf: function(m) {
|
|
|
+ if (!m || !this.scoreMap) return null
|
|
|
+ var v = this.scoreMap[m.id]
|
|
|
+ if (v === undefined || v === null || v === '') return null
|
|
|
+ var n = Number(v)
|
|
|
+ return isNaN(n) ? null : Math.round(n)
|
|
|
+ },
|
|
|
+ onTap: function(m) {
|
|
|
+ if (m && String(m.id) === String(this.selectedMemberId)) return
|
|
|
+ this.$emit('select', m)
|
|
|
+ },
|
|
|
+ onLongPress: function(m) {
|
|
|
+ this.$emit('longpress', m)
|
|
|
+ },
|
|
|
+ onManage: function() {
|
|
|
+ this.$emit('manage')
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+// 维度色映射:选中态渐变用主色 -> 深阶,五维统一从 uni.scss 取令牌
|
|
|
+$tab-dims: (
|
|
|
+ body: $dim-body,
|
|
|
+ mind: $dim-mind,
|
|
|
+ wisdom: $dim-wisdom,
|
|
|
+ wealth: $dim-wealth,
|
|
|
+ action: $dim-action
|
|
|
+);
|
|
|
+
|
|
|
+.member-tabs {
|
|
|
+ padding: 8rpx 30rpx 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.tabs-scroll {
|
|
|
+ width: 100%;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.tabs-inner {
|
|
|
+ display: inline-flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16rpx;
|
|
|
+ padding: 4rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* ---- 成员标签(chip) ---- */
|
|
|
+.member-tab {
|
|
|
+ display: inline-flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ min-height: 88rpx;
|
|
|
+ padding: 10rpx 22rpx 10rpx 12rpx;
|
|
|
+ background: $app-surface;
|
|
|
+ border: 2rpx solid $app-border-subtle;
|
|
|
+ border-radius: 44rpx;
|
|
|
+ transition: border-color $dur-state $ease-standard, background $dur-state $ease-standard, transform $dur-press $ease-standard;
|
|
|
+ flex-shrink: 0;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ transform: scale(0.97);
|
|
|
+ opacity: $press-opacity;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.tab-avatar-wrap {
|
|
|
+ position: relative;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-avatar {
|
|
|
+ width: 64rpx;
|
|
|
+ height: 64rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: $app-surface-subtle;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-self-mark {
|
|
|
+ position: absolute;
|
|
|
+ right: -6rpx;
|
|
|
+ bottom: -6rpx;
|
|
|
+ min-width: 36rpx;
|
|
|
+ height: 36rpx;
|
|
|
+ padding: 0 8rpx;
|
|
|
+ border-radius: 18rpx;
|
|
|
+ background: $brand-primary;
|
|
|
+ color: $app-surface;
|
|
|
+ font-size: 20rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-body {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ margin-left: 14rpx;
|
|
|
+ max-width: 200rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-name {
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: $app-text;
|
|
|
+ line-height: 1.3;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-meta {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 2rpx;
|
|
|
+ gap: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-relation {
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: $app-text-muted;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-score {
|
|
|
+ font-size: 22rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: $app-text-secondary;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-score-unit {
|
|
|
+ font-size: 18rpx;
|
|
|
+ font-weight: 400;
|
|
|
+ margin-left: 1rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* ---- 管理入口 ---- */
|
|
|
+.tab-manage {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-height: 88rpx;
|
|
|
+ min-width: 88rpx;
|
|
|
+ padding: 0 20rpx;
|
|
|
+ border-radius: 44rpx;
|
|
|
+ background: $app-surface-subtle;
|
|
|
+ flex-shrink: 0;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ opacity: $press-opacity;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.tab-manage-icon {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: $app-text-secondary;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+/* ---- 空态 ---- */
|
|
|
+.tabs-empty {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 24rpx 28rpx;
|
|
|
+ background: $app-surface;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ border: 2rpx dashed $app-border;
|
|
|
+}
|
|
|
+
|
|
|
+.tabs-empty-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: $app-text-secondary;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.tabs-empty-btn {
|
|
|
+ margin-top: 16rpx;
|
|
|
+ padding: 12rpx 32rpx;
|
|
|
+ border-radius: 40rpx;
|
|
|
+ background: $brand-primary;
|
|
|
+ color: $app-surface;
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 500;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ opacity: $press-opacity;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ---- 选中态:按维度着色 ---- */
|
|
|
+@each $code, $color in $tab-dims {
|
|
|
+ .dim-#{$code} .member-tab.is-selected {
|
|
|
+ background: linear-gradient(135deg, $color, rgba(red($color), green($color), blue($color), 0.82));
|
|
|
+ border-color: $color;
|
|
|
+
|
|
|
+ .tab-name,
|
|
|
+ .tab-relation,
|
|
|
+ .tab-score {
|
|
|
+ color: $app-surface;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tab-avatar {
|
|
|
+ border: 2rpx solid $app-surface;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|