aslan-h5/src/components/team/TeamBillMore.vue
2025-08-26 11:59:56 +08:00

537 lines
11 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>
<div class="team-bill-more">
<!-- 头部区域 -->
<div class="bill-header">
<h2>{{ billData?.billTitle || 'Team Bill Details' }}</h2>
<div class="header-right">
<span :class="['bill-status', getStatusClass(billData?.status)]">
{{ getStatusText(billData?.status) }}
</span>
<button class="close-btn" @click="$emit('close')">
<span>×</span>
</button>
</div>
</div>
<!-- 成员列表 -->
<div class="members-container">
<div v-if="loading" class="loading-container">
<div class="loading-spinner"></div>
<p>Loading members...</p>
</div>
<div v-else-if="members && members.length > 0" class="members-list">
<div
v-for="(member, index) in members"
:key="member.memberProfile?.id || index"
class="member-item"
>
<!-- 用户头像 -->
<div class="member-avatar">
<img
v-if="member.memberProfile?.userAvatar"
:src="member.memberProfile.userAvatar"
:alt="member.memberProfile?.userNickname"
@error="handleImageError"
/>
<div v-else class="avatar-placeholder">
{{ getAvatarPlaceholder(member.memberProfile?.userNickname) }}
</div>
</div>
<!-- 用户信息区域 -->
<div class="member-details">
<!-- 左侧:用户信息 -->
<div class="user-info">
<div class="user-name">{{ member.memberProfile?.userNickname || 'Unknown User' }}</div>
<div class="user-id">ID: {{ member.memberProfile?.account || 'N/A' }}</div>
<div class="user-target">
<span :class="['target-badge', getTargetClass(member.target?.acceptGiftValue)]">
{{ formatTargetDisplay(member.target?.acceptGiftValue) }}
</span>
</div>
</div>
<!-- 右侧:统计信息 -->
<div class="member-stats">
<div class="diamond-info">
<span class="diamond-icon">🪙</span>
<span class="diamond-value">{{ formatDiamondValue(member.target?.acceptGiftValue) }}</span>
</div>
<div class="time-info">
<span class="time-label">Time (Days):</span>
<span class="time-value">{{ member.target.onlineTime }}</span>
</div>
</div>
</div>
</div>
</div>
<div v-else class="empty-state">
<div class="empty-icon">👥</div>
<p>No members data available</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { formatBillStatus, getTeamReleasePolicy } from '../../api/teamBill.js'
import { getTeamId } from '../../utils/userStore.js'
const props = defineProps({
billData: {
type: Object,
required: true
}
})
const emit = defineEmits(['close'])
const loading = ref(false)
const policyData = ref(null)
const loadingPolicy = ref(false)
// 从 billData 中获取成员数据
const members = computed(() => {
return props.billData?.members || []
})
// 获取政策数据
const fetchPolicyData = async () => {
try {
loadingPolicy.value = true
const teamId = getTeamId()
if (!teamId) {
console.warn('No team ID available')
return
}
const response = await getTeamReleasePolicy(teamId)
if (response && response.status && response.body) {
policyData.value = response.body
console.debug('Policy data loaded:', policyData.value)
}
} catch (error) {
console.error('Failed to fetch policy data:', error)
} finally {
loadingPolicy.value = false
}
}
// 根据礼物价值匹配政策等级
const getTargetLevel = (giftValue) => {
if (!policyData.value?.policy || !giftValue) {
return 'N/A'
}
const numValue = parseFloat(giftValue)
if (isNaN(numValue)) {
return 'N/A'
}
// 按target从大到小排序找到第一个小于等于giftValue的level
const sortedPolicy = [...policyData.value.policy].sort((a, b) => {
return parseFloat(b.target) - parseFloat(a.target)
})
console.log('sortedPolicy', JSON.stringify(sortedPolicy))
console.log('numValue', JSON.stringify(numValue))
for (const policy of sortedPolicy) {
if (numValue >= parseFloat(policy.target)) {
return policy.level
}
}
// 如果都不匹配,返回最低等级
const minLevel = Math.min(...policyData.value.policy.map(p => p.level))
return minLevel
}
// 格式化Target显示
const formatTargetDisplay = (giftValue) => {
if (loadingPolicy.value) {
return 'Loading...'
}
const level = getTargetLevel(giftValue)
return `Level ${level}`
}
// 获取Target等级样式
const getTargetClass = (giftValue) => {
const level = getTargetLevel(giftValue)
if (typeof level === 'number') {
return `target-level-${level}`
}
return 'target-default'
}
// 组件挂载时获取政策数据
onMounted(() => {
fetchPolicyData()
})
// 格式化状态文本
const getStatusText = (status) => {
const statusInfo = formatBillStatus(status)
return statusInfo.text
}
// 获取状态样式类
const getStatusClass = (status) => {
const statusInfo = formatBillStatus(status)
return statusInfo.class
}
// 格式化钻石数值
const formatDiamondValue = (value) => {
if (!value || value === '0') return '1234567890'
// 如果已经是格式化的字符串,直接返回
if (typeof value === 'string' && (value.includes('K') || value.includes('M'))) {
return value.replace(/[^0-9KM.]/g, '')
}
const numValue = parseInt(value)
if (isNaN(numValue)) return '1234567890'
return numValue.toLocaleString()
}
// 计算有效天数
const calculateEffectiveDays = (onlineTime) => {
if (!onlineTime) return '14'
// 如果在线时间大于60分钟则计为1天
const minutes = parseInt(onlineTime)
if (isNaN(minutes)) return '14'
// 简单计算每60分钟算作1天
const days = Math.floor(minutes / 60)
return days > 0 ? days.toString() : '14'
}
// 头像占位符
const getAvatarPlaceholder = (nickname) => {
if (!nickname) return 'U'
return nickname.charAt(0).toUpperCase()
}
// 处理图片加载错误
const handleImageError = (event) => {
console.warn('头像加载失败:', event.target.src)
// 可以在这里设置默认头像
}
</script>
<style scoped>
.team-bill-more {
background-color: #f5f5f5;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
/* 头部区域 */
.bill-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background-color: white;
border-bottom: 1px solid #e5e7eb;
}
.bill-header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
.header-right {
display: flex;
align-items: center;
gap: 12px;
}
.bill-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.bill-status.in-progress {
background-color: #DBEAFE;
color: #1D4ED8;
}
.bill-status.pending {
background-color: #FEF3C7;
color: #D97706;
}
.bill-status.completed {
background-color: #D1FAE5;
color: #059669;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
/* 成员列表容器 */
.members-container {
padding: 8px;
}
.members-list {
display: flex;
flex-direction: column;
gap: 0;
}
/* 成员项目 */
.member-item {
background-color: white;
display: flex;
align-items: center;
padding: 16px;
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s;
}
.member-item:hover {
background-color: #f9f9f9;
}
.member-item:first-child {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
.member-item:last-child {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
border-bottom: none;
}
/* 用户头像 */
.member-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
margin-right: 12px;
}
.member-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-placeholder {
width: 100%;
height: 100%;
background-color: #8B5CF6;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 18px;
font-weight: 600;
}
/* 用户信息区域 */
.member-details {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
/* 左侧用户信息 */
.user-info {
display: flex;
flex-direction: column;
gap: 4px;
}
.user-name {
font-size: 16px;
font-weight: 600;
color: #333;
margin: 0;
}
.user-id {
font-size: 12px;
color: #666;
margin: 0;
}
.user-target {
margin-top: 2px;
}
.target-badge {
background-color: #f3f4f6;
color: #374151;
padding: 2px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
border: 1px solid #d1d5db;
transition: all 0.2s ease;
}
/* 不同等级的颜色样式 */
.target-level-1 {
background-color: #dbeafe;
color: #1d4ed8;
border-color: #93c5fd;
}
.target-level-2 {
background-color: #dcfce7;
color: #166534;
border-color: #86efac;
}
.target-level-3 {
background-color: #fef3c7;
color: #92400e;
border-color: #fbbf24;
}
.target-level-4 {
background-color: #ede9fe;
color: #6b21a8;
border-color: #c4b5fd;
}
.target-level-5 {
background-color: #fce7f3;
color: #be185d;
border-color: #f9a8d4;
}
.target-default {
background-color: #f3f4f6;
color: #6b7280;
border-color: #d1d5db;
}
/* 右侧统计信息 */
.member-stats {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.diamond-info {
display: flex;
align-items: center;
gap: 4px;
}
.diamond-icon {
font-size: 14px;
}
.diamond-value {
font-size: 14px;
font-weight: 600;
color: #333;
}
.time-info {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
}
.time-label {
color: #666;
}
.time-value {
font-weight: 600;
color: #333;
}
/* 加载状态 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
color: #666;
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid #f3f3f3;
border-top: 3px solid #8B5CF6;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 12px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 空数据状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px;
color: #666;
}
.empty-icon {
font-size: 48px;
margin-bottom: 12px;
}
.empty-state p {
margin: 0;
font-size: 14px;
}
/* 响应式设计 */
@media (max-width: 480px) {
.member-details {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.member-stats {
align-self: flex-end;
align-items: flex-end;
}
}
</style>