aslan-h5/src/components/team/TeamBillMore.vue
2025-11-04 16:31:36 +08:00

710 lines
16 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
style="
background: white;
border-radius: 16px 16px 0 0;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
gap: 10px;
padding: 20px;
"
>
<!-- 标题 -->
<div style="display: flex; justify-content: center; align-items: center">
<div style="margin: 0; font-size: 18px; font-weight: 600; color: #333">
{{ t('team_member') }}
</div>
</div>
<!-- 日期和类型 -->
<div style="display: flex; justify-content: space-between; align-items: center">
<div style="color: rgba(0, 0, 0, 0.5); font-weight: 600; font-size: 1.3em">
{{ billData?.date }}
</div>
<div
style="margin: 4px; padding: 0 8px; border-radius: 4px; display: flex; align-items: center"
:style="{ boxShadow }"
>
<div style="font-weight: 500" :style="{ color }">
{{ getStatusText(billData?.status) }}
</div>
</div>
</div>
<!-- 搜索框 -->
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px">
<div
style="
flex: 1;
border-radius: 32px;
border: 1px solid #fff;
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.25);
padding: 0 8px;
display: flex;
align-items: center;
height: max-content;
"
>
<img src="../../assets/icon/search.png" alt="" width="24px" style="opacity: 0.6" />
<input
type="text"
name=""
id=""
:placeholder="t('enter_host_id')"
style="
width: 100%;
outline: none;
border: none;
background-color: transparent;
color: black;
padding: 10px 8px;
"
v-model="targetUserId"
/>
</div>
<div style="font-weight: 600" @click="$emit('close')">{{ t('cancel') }}</div>
</div>
<!-- 加载成员 -->
<div v-if="loading" class="loading-container">
<div class="loading-spinner"></div>
<p>{{ t('loading') }}...</p>
</div>
<!-- 成员列表 -->
<div v-if="members && members.length > 0" class="members-list">
<div
v-for="(member, index) in members"
:key="member.memberProfile?.id || index"
style="
background-color: white;
padding: 16px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: space-between;
"
>
<!-- 用户信息 -->
<div style="width: 70%; display: flex; align-items: center; justify-content: space-around">
<!-- 头像 -->
<div
style="
position: relative;
margin-right: 4px;
width: 20%;
aspect-ratio: 1/1;
border-radius: 50%;
background-color: #8b5cf6;
color: white;
font-size: 18px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
"
>
<img
v-if="member.memberProfile?.userAvatar"
:src="member.memberProfile.userAvatar"
:alt="member.memberProfile?.userNickname"
@error="handleImageError"
style="width: 100%; height: 100%; border-radius: 50%; object-fit: cover"
/>
<div
v-else
style="
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-transform: uppercase;
"
>
{{ getAvatarPlaceholder(member.memberProfile?.userNickname) }}
</div>
</div>
<!-- 账号信息 -->
<div style="width: 35%; display: flex; flex-direction: column; gap: 4px">
<div
style="
font-weight: 700;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.9em;
"
>
{{ member.memberProfile?.userNickname || t('unknown_user') }}
</div>
<div
style="
color: rgba(0, 0, 0, 0.4);
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.9em;
"
>
ID: {{ member.memberProfile?.account || 'N/A' }}
</div>
</div>
<!-- 等级信息 -->
<div style="display: flex; align-items: center">
<div
style="
border-radius: 12px;
background: linear-gradient(
152deg,
rgba(198, 112, 255, 0.6) 7.01%,
rgba(119, 38, 255, 0.6) 92.99%
);
color: #fff;
padding: 2px 4px;
font-weight: 500;
border: 1px solid #d1d5db;
overflow: hidden;
white-space: nowrap;
font-size: 0.9em;
"
>
{{ formatTargetDisplay(member.target?.acceptGiftValue) }}
</div>
</div>
</div>
<!-- 数据区域 -->
<div style="width: 30%; display: flex; justify-content: flex-end">
<div style="width: 100%; display: flex; flex-direction: column">
<div style="width: 100%; display: flex; align-items: center">
<img src="../../assets/icon/coin.png" alt="" height="16px" style="display: block" />
<div
style="
color: #ffb627;
font-weight: 510;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
font-size: 0.9em;
"
>
{{ formatDiamondValue(member.target?.acceptGiftValue) }}
</div>
</div>
<div
style="
color: rgba(0, 0, 0, 0.4);
font-weight: 510;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
font-size: 0.7em;
"
>
{{ t('time_days') }}:{{ Math.floor(member.target.onlineTime / 60 / 2, 0) }}
</div>
</div>
</div>
</div>
</div>
<div v-else class="empty-state">
<div class="empty-icon">👥</div>
<p>{{ t('no_data_available') }}</p>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useLangStore } from '@/stores/lang'
import { formatBillStatus, getTeamReleasePolicy } from '../../api/teamBill.js'
import { getTeamId } from '../../utils/userStore.js'
import { setDocumentDirection } from '@/locales/i18n'
const { t, locale } = useI18n()
// 监听语言变化并设置文档方向
locale.value && setDocumentDirection(locale.value)
defineEmits(['close'])
const props = defineProps({
billData: {
type: Object,
required: true,
},
})
const loading = ref(false)
const policyData = ref(null)
const loadingPolicy = ref(false)
const targetUserId = ref('') //搜索目标id
// 样式
const color = ref('')
const boxShadow = ref('')
const typeShowText = ref('')
const langStore = useLangStore()
// 从 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 t('loading') + '...'
}
const level = getTargetLevel(giftValue)
return `${t('target')}: ${level}`
}
// 格式化状态文本
const getStatusText = (status) => {
// const statusInfo = formatBillStatus(status)
// 使用翻译键而不是直接文本
switch (status) {
case 'Completed':
return t('completed')
case 'Pending':
return t('pending')
case 'Out of account':
return t('out_of_account')
case 'In Progress':
return t('in_progress')
default:
return statusInfo.textKey
}
}
// 获取状态样式类
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)
// 可以在这里设置默认头像
}
// 更新展示类型
const updateTypeShowText = () => {
console.log('更新展示类型')
let type = props.billData?.status
if (type == 'In Progress') {
typeShowText.value = t('in_progress')
} else if (type == 'Pending') {
typeShowText.value = t('pending')
} else if (type == 'Completed') {
typeShowText.value = t('completed')
} else if (type == 'Out of account') {
typeShowText.value = t('out_of_account')
}
}
// 监听语言变化确保UI更新
watch(
() => langStore.selectedLang,
(newLang) => {
console.log('Language changed in header:', newLang)
updateTypeShowText()
},
{ deep: true }
)
watch(
() => props.billData?.status,
(newType) => {
console.log('newType:', newType)
// let newStatus = getStatusText(status)
updateTypeShowText()
if (newType == 'In Progress') {
boxShadow.value = '0 0 1px 0 #6085FF'
color.value = 'rgba(61, 115, 255, 1)'
}
if (newType == 'Pending') {
boxShadow.value = '0 0 1px 0 #FF6062'
color.value = 'rgba(255, 61, 64, 1)'
}
if (newType == 'Completed') {
boxShadow.value = '0 0 1px 0 #60FF65'
color.value = 'rgba(61, 255, 84, 1)'
}
if (newType == 'Out of account') {
boxShadow.value = '0 0 1px 0 #6A6161'
color.value = 'rgba(174, 174, 174, 1)'
}
return
},
{
immediate: true,
}
)
// 组件挂载时获取政策数据
onMounted(() => {
console.log('locale.value:', locale.value)
fetchPolicyData()
})
</script>
<style scoped>
* {
color: rgba(0, 0, 0, 0.8);
font-family: 'SF Pro Text';
}
input::placeholder {
font-weight: bold;
color: rgba(0, 0, 0, 0.4);
}
.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;
}
/* 成员列表容器 */
.members-container {
padding: 8px;
}
.members-list {
display: flex;
flex-direction: column;
gap: 12px;
}
/* 成员项目 */
.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;
}
/* 右侧统计信息 */
.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;
}
}
@media screen and (max-width: 360px) {
* {
font-size: 10px;
}
}
@media screen and (min-width: 360px) {
* {
font-size: 12px;
}
}
@media screen and (min-width: 768px) {
* {
font-size: 24px;
}
}
</style>